kiku-agent 1.0.5 → 1.0.7
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 +46 -19
- package/package.json +11 -1
- package/server/agent/agentCore.js +164 -55
- package/server/agent/cli.js +1315 -701
- package/server/agent/commandRegistry.js +436 -74
- package/server/agent/commands.js +640 -68
- package/server/agent/config.js +64 -48
- package/server/agent/doctor.js +7 -6
- package/server/agent/llmClient.js +26 -19
- package/server/agent/mcpClient.js +436 -364
- package/server/agent/modelManager.js +30 -13
- package/server/agent/queryDetails.js +182 -0
- package/server/agent/sanitizer.js +4 -4
- package/server/agent/subagent.js +1 -0
- package/server/agent/suggestions.js +346 -0
- package/server/agent/theme.js +12 -12
- package/server/agent/tools.js +118 -7
- package/server/agent/ui.js +433 -31
- package/server/agent/updateManager.js +438 -0
- package/server/agent/version.js +10 -0
- package/server/agent/youtubeAuth.js +1 -2
- package/server/mcp/data/brands.js +9 -4
- package/server/mcp/server.js +2 -1
- package/src/data/brandsData.js +65 -0
package/server/agent/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import readline from 'node:readline';
|
|
4
|
-
import { stdin as input } from 'node:process';
|
|
5
|
-
import { existsSync } from 'node:fs';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
7
|
-
import
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { stdin as input } from 'node:process';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import './youtubeAuth.js';
|
|
8
|
+
import { KikuAgent } from './agentCore.js';
|
|
8
9
|
import { LLMClient, LMStudioClient } from './llmClient.js';
|
|
9
10
|
import { toolsSchema, toolHandlers, executeTool } from './tools.js';
|
|
10
11
|
import { config, CLOUD_PROVIDERS } from './config.js';
|
|
@@ -14,18 +15,30 @@ import { eventBus, Events } from './eventBus.js';
|
|
|
14
15
|
import { AgentStates } from './agentState.js';
|
|
15
16
|
import { sanitizeQuery } from './sanitizer.js';
|
|
16
17
|
import { isAbortError } from './errors.js';
|
|
17
|
-
import { COMMAND_DEFINITIONS, SUB_COMMANDS, findCommandDefinition } from './commandRegistry.js';
|
|
18
|
+
import { COMMAND_DEFINITIONS, SUB_COMMANDS, findCommandDefinition, getCommandDetails } from './commandRegistry.js';
|
|
18
19
|
import { ModelManager } from './modelManager.js';
|
|
20
|
+
import { PACKAGE_VERSION } from './version.js';
|
|
21
|
+
import { getTheme, getThemeName } from './theme.js';
|
|
22
|
+
import {
|
|
23
|
+
classifyToolResult,
|
|
24
|
+
formatDetailDuration,
|
|
25
|
+
parseDetailsRequest,
|
|
26
|
+
serializeDetailValue,
|
|
27
|
+
summarizeQueryDetails,
|
|
28
|
+
copyToClipboard,
|
|
29
|
+
getCopyableContent,
|
|
30
|
+
} from './queryDetails.js';
|
|
19
31
|
|
|
20
32
|
import {
|
|
21
|
-
C, getStringDisplayWidth, getFullWidth, wrapText, stripAnsi, fitStyledText,
|
|
33
|
+
C, getStringDisplayWidth, getFullWidth, wrapText, stripAnsi, fitStyledText, sanitizeTerminalText,
|
|
22
34
|
previousGraphemeBoundary, nextGraphemeBoundary, sliceLineForDisplay,
|
|
23
35
|
printSolidBannerRow, printSolidPanel, formatSolidPanelLines, formatTerminalMarkdown,
|
|
24
36
|
parseMarkdownTableRow, isMarkdownTableSeparator, expandMarkdownLinks,
|
|
37
|
+
formatRichCardLines, printRichCard, printRichStatusCard, formatStatusBadge, formatRichTable,
|
|
25
38
|
printWelcomeHero, getWelcomeHeroLines, randomFanName, setupOutputInterception,
|
|
26
39
|
rawWrite, INPUT_PLACEHOLDER, UI_ICONS,
|
|
27
40
|
spinnerFrames, thinkingFrames, createProgressRunner,
|
|
28
|
-
enterAlternateScreen, exitAlternateScreen, VirtualScreenBuffer,
|
|
41
|
+
enterAlternateScreen, exitAlternateScreen, getTuiViewportLayout, VirtualScreenBuffer,
|
|
29
42
|
} from './ui.js';
|
|
30
43
|
|
|
31
44
|
let activeAbortController = null;
|
|
@@ -53,12 +66,14 @@ function parseCliArgs() {
|
|
|
53
66
|
showStats: false,
|
|
54
67
|
thinking: 'show',
|
|
55
68
|
verbose: false,
|
|
56
|
-
help: false
|
|
69
|
+
help: false,
|
|
70
|
+
version: false
|
|
57
71
|
};
|
|
58
72
|
|
|
59
73
|
for (let i = 0; i < args.length; i++) {
|
|
60
74
|
const arg = args[i];
|
|
61
75
|
if (arg === '--help' || arg === '-h') options.help = true;
|
|
76
|
+
else if (arg === '--version' || arg === '-V') options.version = true;
|
|
62
77
|
else if (arg === '--tools' || arg === '-t') options.showTools = true;
|
|
63
78
|
else if (arg === '--stats' || arg === '-s') options.showStats = true;
|
|
64
79
|
else if (arg === '--verbose' || arg === '-v') { options.verbose = true; options.thinking = 'show'; }
|
|
@@ -91,10 +106,10 @@ async function attemptAutoStartLMStudio(baseUrl, model) {
|
|
|
91
106
|
|
|
92
107
|
const candidates = isWin
|
|
93
108
|
? [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
`${process.env.LOCALAPPDATA}\\LM Studio\\LM Studio.exe`,
|
|
110
|
+
`${process.env.LOCALAPPDATA}\\Programs\\LM Studio\\LM Studio.exe`,
|
|
111
|
+
'C:\\Program Files\\LM Studio\\LM Studio.exe'
|
|
112
|
+
]
|
|
98
113
|
: isMac
|
|
99
114
|
? ['/Applications/LM Studio.app/Contents/MacOS/LM Studio']
|
|
100
115
|
: ['lmstudio', '/usr/local/bin/lmstudio', '/opt/lmstudio/bin/lmstudio'];
|
|
@@ -107,11 +122,15 @@ async function attemptAutoStartLMStudio(baseUrl, model) {
|
|
|
107
122
|
});
|
|
108
123
|
|
|
109
124
|
if (!exe) return null;
|
|
110
|
-
console.log(` ${C.cyberCyan}attempting to auto-start LM Studio...${C.reset}`);
|
|
111
125
|
|
|
112
126
|
try {
|
|
113
127
|
let port = '1234';
|
|
114
|
-
try { const parsedUrl = new URL(baseUrl); if (parsedUrl.port) port = parsedUrl.port; } catch {}
|
|
128
|
+
try { const parsedUrl = new URL(baseUrl); if (parsedUrl.port) port = parsedUrl.port; } catch { }
|
|
129
|
+
printRichCard({
|
|
130
|
+
title: 'LM Studio Auto-Start',
|
|
131
|
+
status: 'autostart',
|
|
132
|
+
lines: [`Launching local LM Studio process on port ${port}...`],
|
|
133
|
+
});
|
|
115
134
|
|
|
116
135
|
const child = spawn(exe, isWin
|
|
117
136
|
? ['--model', model, '--serve', '--port', port]
|
|
@@ -125,12 +144,11 @@ async function attemptAutoStartLMStudio(baseUrl, model) {
|
|
|
125
144
|
}
|
|
126
145
|
|
|
127
146
|
function extractPreview(result, toolName) {
|
|
128
|
-
|
|
147
|
+
if (!result || typeof result !== 'object') return '';
|
|
129
148
|
let raw = '';
|
|
130
149
|
if (result.content) raw = result.content;
|
|
131
150
|
else if (result.results && Array.isArray(result.results) && result.results.length > 0) {
|
|
132
|
-
|
|
133
|
-
raw = first.title || first.snippet || first.url || '';
|
|
151
|
+
raw = result.results.map(r => r.title || r.name || r.id || JSON.stringify(r)).join(', ');
|
|
134
152
|
} else if (result.works) raw = result.works.map(w => w.titleTh || w.titleCn).filter(Boolean).join(', ');
|
|
135
153
|
else if (result.songs) raw = result.songs.map(s => s.title).filter(Boolean).join(', ');
|
|
136
154
|
else if (result.brands) raw = result.brands.map(b => b.brand).filter(Boolean).join(', ');
|
|
@@ -138,8 +156,7 @@ function extractPreview(result, toolName) {
|
|
|
138
156
|
else if (result.stats) raw = `${result.stats.totalWorks || 0} works`;
|
|
139
157
|
|
|
140
158
|
if (!raw) return '';
|
|
141
|
-
|
|
142
|
-
return raw.length > MAX ? raw.slice(0, MAX) + '…' : raw;
|
|
159
|
+
return raw.replace(/\s+/g, ' ').trim();
|
|
143
160
|
}
|
|
144
161
|
|
|
145
162
|
function cleanThinkingLine(line) {
|
|
@@ -153,77 +170,263 @@ function cleanThinkingLine(line) {
|
|
|
153
170
|
}
|
|
154
171
|
|
|
155
172
|
function printQueryDetails(reference = '') {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
173
|
+
const request = parseDetailsRequest(reference);
|
|
174
|
+
if (request.error) {
|
|
175
|
+
printSolidPanel({
|
|
176
|
+
title: 'Query Details — Invalid Request',
|
|
177
|
+
lines: [
|
|
178
|
+
`${C.amber}${request.error}${C.reset}`,
|
|
179
|
+
'',
|
|
180
|
+
`${C.white}/details [Qn]${C.reset} ${C.dim}— overview${C.reset}`,
|
|
181
|
+
`${C.white}/details [Qn] answer${C.reset}`,
|
|
182
|
+
`${C.white}/details [Qn] thinking${C.reset}`,
|
|
183
|
+
`${C.white}/details [Qn] tools${C.reset}`,
|
|
184
|
+
`${C.white}/details [Qn] tool <n>${C.reset}`,
|
|
185
|
+
`${C.white}/details [Qn] all${C.reset}`,
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const details = request.queryNumber == null
|
|
192
|
+
? lastQueryDetails
|
|
193
|
+
: queryDetails.get(request.queryNumber);
|
|
161
194
|
|
|
162
195
|
if (!details) {
|
|
163
|
-
|
|
196
|
+
const target = request.queryNumber == null ? 'the latest query' : `Q${request.queryNumber}`;
|
|
197
|
+
console.log(`\n ${C.amber}[NOT FOUND] No diagnostics recorded for ${target}.${C.reset}\n`);
|
|
164
198
|
return;
|
|
165
199
|
}
|
|
166
200
|
|
|
167
201
|
const width = Math.max(16, getFullWidth() - 8);
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
return wrapText(
|
|
171
|
-
.map(line => `${
|
|
202
|
+
const safeWrappedLines = (value, color = C.white) => {
|
|
203
|
+
const inputLines = String(value || '—').split('\n');
|
|
204
|
+
return inputLines.flatMap(line => wrapText(sanitizeTerminalText(line, 500000) || ' ', width))
|
|
205
|
+
.map(line => `${color}${line}${C.reset}`);
|
|
206
|
+
};
|
|
207
|
+
const summary = summarizeQueryDetails(details);
|
|
208
|
+
const queryLabel = `Q${details.number}`;
|
|
209
|
+
const statusColor = details.status === 'complete' ? C.emerald : details.status === 'aborted' ? C.amber : C.red;
|
|
210
|
+
const statusLabel = String(details.status || 'complete').toUpperCase();
|
|
211
|
+
const startedLabel = Number.isFinite(details.startedAt)
|
|
212
|
+
? new Date(details.startedAt).toLocaleString()
|
|
213
|
+
: 'n/a';
|
|
214
|
+
|
|
215
|
+
const toolTimelineLines = (includeArgs = false) => {
|
|
216
|
+
if (!details.tools?.length) return [`${C.dim}${C.slate}No tools were used.${C.reset}`];
|
|
217
|
+
return details.tools.flatMap((tool, index) => {
|
|
218
|
+
const resultState = classifyToolResult(tool.result);
|
|
219
|
+
const stateColor = resultState.state === 'ok' ? C.emerald : resultState.state === 'not_found' ? C.amber : C.red;
|
|
220
|
+
const countLabel = resultState.count == null ? '' : ` • ${resultState.count} results`;
|
|
221
|
+
const preview = extractPreview(tool.result || {}, tool.name);
|
|
222
|
+
const rows = [
|
|
223
|
+
`${C.slate}${String(index + 1).padStart(2)}.${C.reset} ${C.bold}${C.cyberCyan}${sanitizeTerminalText(tool.name, 128)}${C.reset} ${stateColor}[${resultState.state.toUpperCase()}]${C.reset} ${C.dim}${formatDetailDuration(tool.duration)}${countLabel}${C.reset}`,
|
|
224
|
+
];
|
|
225
|
+
if (includeArgs) {
|
|
226
|
+
const serializedArgs = serializeDetailValue(tool.args, Infinity).text.replace(/\s+/g, ' ');
|
|
227
|
+
rows.push(...safeWrappedLines(` Args: ${serializedArgs}`, C.slate));
|
|
228
|
+
}
|
|
229
|
+
if (preview) rows.push(...safeWrappedLines(` Result: ${preview}`, C.slate));
|
|
230
|
+
return rows;
|
|
231
|
+
});
|
|
172
232
|
};
|
|
173
233
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
234
|
+
if (request.view === 'answer') {
|
|
235
|
+
printSolidPanel({
|
|
236
|
+
title: `Answer • ${queryLabel}`,
|
|
237
|
+
lines: details.answer
|
|
238
|
+
? String(details.answer).split('\n').flatMap(line => wrapText(sanitizeTerminalText(line, 500000), width)).map(line => `${C.white}${formatTerminalMarkdown(line)}${C.reset}`)
|
|
239
|
+
: [`${C.dim}${C.slate}No final answer was recorded.${C.reset}`],
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
178
243
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
244
|
+
if (request.view === 'thinking') {
|
|
245
|
+
const thinking = String(details.thinking || '').trim();
|
|
246
|
+
printSolidPanel({
|
|
247
|
+
title: `Thinking • ${queryLabel} • ${summary.thinkingCharacters} chars`,
|
|
248
|
+
lines: thinking
|
|
249
|
+
? thinking.split('\n').flatMap(line => wrapText(sanitizeTerminalText(cleanThinkingLine(line), 500000), width)).map(line => `${C.dim}${C.slate}${line}${C.reset}`)
|
|
250
|
+
: [`${C.dim}${C.slate}No thinking trace was recorded.${C.reset}`],
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
186
254
|
|
|
187
|
-
if (
|
|
255
|
+
if (request.view === 'tools') {
|
|
188
256
|
printSolidPanel({
|
|
189
|
-
title: `
|
|
190
|
-
lines: [
|
|
257
|
+
title: `Tool Timeline • ${queryLabel} • ${summary.toolCount} calls`,
|
|
258
|
+
lines: [
|
|
259
|
+
...toolTimelineLines(true),
|
|
260
|
+
'',
|
|
261
|
+
`${C.dim}Open raw data with /details ${queryLabel} tool <n>.${C.reset}`,
|
|
262
|
+
],
|
|
191
263
|
});
|
|
192
264
|
return;
|
|
193
265
|
}
|
|
194
266
|
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
267
|
+
if (request.view === 'tool') {
|
|
268
|
+
const tool = details.tools?.[request.toolIndex - 1];
|
|
269
|
+
if (!tool) {
|
|
270
|
+
console.log(`\n ${C.amber}[NOT FOUND] ${queryLabel} has no tool call #${request.toolIndex}.${C.reset}\n`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const args = serializeDetailValue(tool.args, Infinity);
|
|
274
|
+
const result = serializeDetailValue(tool.result, Infinity);
|
|
275
|
+
const rawLines = (label, serialized) => [
|
|
276
|
+
`${C.bold}${C.lavender}${label}:${C.reset}`,
|
|
277
|
+
...serialized.text.split('\n').flatMap(line => safeWrappedLines(line, C.slate)),
|
|
205
278
|
];
|
|
206
|
-
printSolidPanel({
|
|
279
|
+
printSolidPanel({
|
|
280
|
+
title: `Tool ${request.toolIndex} • ${sanitizeTerminalText(tool.name, 128)} • ${queryLabel}`,
|
|
281
|
+
lines: [
|
|
282
|
+
`${C.gold}Duration:${C.reset} ${C.white}${formatDetailDuration(tool.duration)}${C.reset}`,
|
|
283
|
+
...rawLines('Arguments', args),
|
|
284
|
+
...rawLines('Result', result),
|
|
285
|
+
],
|
|
286
|
+
});
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (request.view === 'markdown' || request.view === 'all') {
|
|
291
|
+
const copyable = getCopyableContent(details, request);
|
|
292
|
+
printSolidPanel({
|
|
293
|
+
title: copyable.title,
|
|
294
|
+
lines: copyable.text.split('\n').flatMap(line => wrapText(line, width)).map(line => `${C.white}${line}${C.reset}`),
|
|
295
|
+
});
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
printSolidPanel({
|
|
300
|
+
title: `Query Diagnostics • ${queryLabel}`,
|
|
301
|
+
lines: [
|
|
302
|
+
`${C.gold}Status:${C.reset} ${statusColor}${statusLabel}${C.reset}${details.error ? ` ${C.red}• ${sanitizeTerminalText(details.error, 240)}${C.reset}` : ''}`,
|
|
303
|
+
`${C.gold}Runtime:${C.reset} ${C.white}${sanitizeTerminalText(details.provider || 'unknown', 80)} / ${sanitizeTerminalText(details.model || 'unknown', 160)}${C.reset}`,
|
|
304
|
+
`${C.gold}Duration:${C.reset} ${C.white}${formatDetailDuration(details.durationMs)}${C.reset} ${C.dim}• ${details.iterations || 0}/${details.maxIterations || '?'} iterations${C.reset}`,
|
|
305
|
+
`${C.gold}Tools:${C.reset} ${C.white}${summary.toolCount} calls${C.reset} ${C.dim}• ${summary.successfulTools} ok • ${summary.emptyTools} empty • ${summary.failedTools} failed • ${formatDetailDuration(summary.toolDurationMs)}${C.reset}`,
|
|
306
|
+
`${C.gold}Output:${C.reset} ${C.white}${summary.answerCharacters} answer chars${C.reset} ${C.dim}• ${summary.thinkingCharacters} thinking chars • ${details.language || 'n/a'}${C.reset}`,
|
|
307
|
+
`${C.gold}Started:${C.reset} ${C.dim}${startedLabel}${C.reset}`,
|
|
308
|
+
'',
|
|
309
|
+
`${C.bold}${C.lavender}Question:${C.reset}`,
|
|
310
|
+
...safeWrappedLines(details.prompt),
|
|
311
|
+
'',
|
|
312
|
+
`${C.bold}${C.lavender}Tool timeline:${C.reset}`,
|
|
313
|
+
...toolTimelineLines(false),
|
|
314
|
+
'',
|
|
315
|
+
`${C.dim}Views: /details ${queryLabel} answer | thinking | tools | tool <n> | all${C.reset}`,
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function copyQueryDetails(reference = '') {
|
|
321
|
+
const request = parseDetailsRequest(reference);
|
|
322
|
+
if (request.error) {
|
|
323
|
+
printSolidPanel({
|
|
324
|
+
title: 'Copy Details — Invalid Request',
|
|
325
|
+
lines: [
|
|
326
|
+
`${C.amber}${request.error}${C.reset}`,
|
|
327
|
+
'',
|
|
328
|
+
`${C.white}/copy [Qn] [answer]${C.reset} ${C.dim}— copy model answer (default)${C.reset}`,
|
|
329
|
+
`${C.white}/copy [Qn] thinking${C.reset} ${C.dim}— copy thinking trace${C.reset}`,
|
|
330
|
+
`${C.white}/copy [Qn] tools${C.reset} ${C.dim}— copy tools JSON${C.reset}`,
|
|
331
|
+
`${C.white}/copy [Qn] tool <n>${C.reset} ${C.dim}— copy tool <n> raw result${C.reset}`,
|
|
332
|
+
`${C.white}/copy [Qn] all${C.reset} ${C.dim}— copy full question & answer markdown${C.reset}`,
|
|
333
|
+
],
|
|
334
|
+
});
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const details = request.queryNumber == null
|
|
339
|
+
? lastQueryDetails
|
|
340
|
+
: queryDetails.get(request.queryNumber);
|
|
341
|
+
|
|
342
|
+
if (!details) {
|
|
343
|
+
const target = request.queryNumber == null ? 'the latest query' : `Q${request.queryNumber}`;
|
|
344
|
+
console.log(`\n ${C.amber}[NOT FOUND] No diagnostics recorded for ${target}.${C.reset}\n`);
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const copyable = getCopyableContent(details, request);
|
|
349
|
+
if (!copyable || !copyable.text) {
|
|
350
|
+
console.log(`\n ${C.amber}[EMPTY] Nothing to copy for ${request.view || 'answer'}.${C.reset}\n`);
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const copied = copyToClipboard(copyable.text);
|
|
355
|
+
const lineCount = copyable.text.split('\n').length;
|
|
356
|
+
const charCount = copyable.text.length.toLocaleString();
|
|
357
|
+
|
|
358
|
+
printRichCard({
|
|
359
|
+
title: copied ? 'Copied to Clipboard' : 'Copy Content Ready',
|
|
360
|
+
status: copied ? 'ok' : 'info',
|
|
361
|
+
lines: [
|
|
362
|
+
`• Target: ${copyable.title}`,
|
|
363
|
+
`• Characters: ${charCount} chars (${lineCount} lines)`,
|
|
364
|
+
...(copied
|
|
365
|
+
? ['• Status: Copied to system clipboard (Ready to paste with Ctrl+V / Cmd+V)']
|
|
366
|
+
: ['• Status: Clipboard utility unavailable, displaying content below:']),
|
|
367
|
+
],
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
if (!copied) {
|
|
371
|
+
const width = Math.max(16, getFullWidth() - 8);
|
|
372
|
+
printSolidPanel({
|
|
373
|
+
title: copyable.title,
|
|
374
|
+
lines: copyable.text.split('\n').flatMap(l => wrapText(l, width)).map(l => `${C.white}${l}${C.reset}`),
|
|
375
|
+
});
|
|
207
376
|
}
|
|
377
|
+
|
|
378
|
+
return true;
|
|
208
379
|
}
|
|
209
380
|
|
|
210
381
|
export function printHelp(specificCmd = null) {
|
|
211
382
|
if (specificCmd) {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
383
|
+
const rawName = specificCmd.trim().toLowerCase();
|
|
384
|
+
const cmdName = rawName.startsWith('/') ? rawName : `/${rawName}`;
|
|
385
|
+
const definition = findCommandDefinition(cmdName);
|
|
386
|
+
const details = getCommandDetails(cmdName);
|
|
387
|
+
|
|
388
|
+
if (!definition && !details) {
|
|
389
|
+
printRichCard({
|
|
390
|
+
title: 'Unknown Command',
|
|
391
|
+
status: 'warning',
|
|
392
|
+
lines: [
|
|
393
|
+
`Command "${specificCmd}" was not found.`,
|
|
394
|
+
'Type /help to see all available commands.',
|
|
395
|
+
],
|
|
396
|
+
});
|
|
216
397
|
return;
|
|
217
398
|
}
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
const
|
|
399
|
+
|
|
400
|
+
const titleName = definition?.name || cmdName;
|
|
401
|
+
const summary = details?.summary || definition?.desc || 'Command description';
|
|
402
|
+
const usage = details?.usage || definition?.usage || `${titleName} [options]`;
|
|
403
|
+
|
|
404
|
+
const lines = [
|
|
405
|
+
`${C.bold}${C.white}${summary}${C.reset}`,
|
|
406
|
+
'',
|
|
407
|
+
`${C.bold}${C.lavender}Usage:${C.reset} ${C.cyberCyan}${usage}${C.reset}`,
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
if (details?.subcommands && details.subcommands.length > 0) {
|
|
411
|
+
lines.push('', `${C.bold}${C.roseGlow}Subcommands & Options:${C.reset}`);
|
|
412
|
+
for (const sub of details.subcommands) {
|
|
413
|
+
lines.push(` ${C.bold}${C.gold}${sub.cmd.padEnd(24)}${C.reset} ${C.white}${sub.desc}${C.reset}`);
|
|
414
|
+
if (sub.example) {
|
|
415
|
+
lines.push(` ${C.dim}↳ Example: ${C.cyberCyan}${sub.example}${C.reset}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (details?.examples && details.examples.length > 0) {
|
|
421
|
+
lines.push('', `${C.bold}${C.emerald}Quick Examples:${C.reset}`);
|
|
422
|
+
for (const ex of details.examples) {
|
|
423
|
+
lines.push(` ${C.cyberCyan}❯ ${ex}${C.reset}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
221
427
|
printSolidPanel({
|
|
222
|
-
title: `${UI_ICONS.help} ${
|
|
223
|
-
lines
|
|
224
|
-
...wrappedDesc.map(d => `${C.white}${d}${C.reset}`),
|
|
225
|
-
...wrappedUsage.map(u => `${C.dim}${u}${C.reset}`),
|
|
226
|
-
],
|
|
428
|
+
title: `${UI_ICONS.help} Command Help • ${titleName}`,
|
|
429
|
+
lines,
|
|
227
430
|
});
|
|
228
431
|
return;
|
|
229
432
|
}
|
|
@@ -247,7 +450,7 @@ export function printHelp(specificCmd = null) {
|
|
|
247
450
|
}
|
|
248
451
|
}
|
|
249
452
|
|
|
250
|
-
helpLines.push(`${C.dim}Tip: Press Tab for auto-completion • Type /help <command> for
|
|
453
|
+
helpLines.push('', `${C.dim}Tip: Press Tab for auto-completion • Type /help <command> (e.g. /help suggestion, /help mcp, /help provider) for detailed subcommand breakdowns.${C.reset}`);
|
|
251
454
|
|
|
252
455
|
printSolidPanel({
|
|
253
456
|
title: `${UI_ICONS.help} Kiku AI Agent — ${COMMAND_DEFINITIONS.length} Commands`,
|
|
@@ -302,6 +505,7 @@ class InteractiveTuiPrompt {
|
|
|
302
505
|
this.isResolving = false;
|
|
303
506
|
this.activeResolve = null;
|
|
304
507
|
this.activeReject = null;
|
|
508
|
+
this.onCancel = options.onCancel || null;
|
|
305
509
|
this.onSigInt = options.onSigInt || null;
|
|
306
510
|
this._keyHandler = null;
|
|
307
511
|
this._renderTimer = null;
|
|
@@ -318,18 +522,24 @@ class InteractiveTuiPrompt {
|
|
|
318
522
|
}
|
|
319
523
|
|
|
320
524
|
addViewportLines(lines) {
|
|
321
|
-
if (
|
|
525
|
+
if (lines === null || lines === undefined) return;
|
|
322
526
|
if (!Array.isArray(lines)) lines = [lines];
|
|
527
|
+
const wasLive = this.scrollOffset === 0;
|
|
323
528
|
for (const l of lines) {
|
|
324
529
|
if (typeof l === 'string') {
|
|
325
530
|
const sublines = l.split('\n');
|
|
326
|
-
for (const sl of sublines)
|
|
531
|
+
for (const sl of sublines) {
|
|
532
|
+
this.viewportLines.push(sl);
|
|
533
|
+
}
|
|
534
|
+
} else if (l != null) {
|
|
535
|
+
this.viewportLines.push(String(l));
|
|
327
536
|
}
|
|
328
537
|
}
|
|
329
538
|
if (this.viewportLines.length > 5000) {
|
|
330
|
-
|
|
539
|
+
const removedCount = this.viewportLines.length - 5000;
|
|
540
|
+
this.viewportLines.splice(0, removedCount);
|
|
331
541
|
}
|
|
332
|
-
if (
|
|
542
|
+
if (wasLive) {
|
|
333
543
|
this.scrollOffset = 0;
|
|
334
544
|
}
|
|
335
545
|
this.render();
|
|
@@ -351,7 +561,7 @@ class InteractiveTuiPrompt {
|
|
|
351
561
|
}
|
|
352
562
|
|
|
353
563
|
clearPrompt() {
|
|
354
|
-
|
|
564
|
+
|
|
355
565
|
}
|
|
356
566
|
|
|
357
567
|
clearPopover() {
|
|
@@ -365,7 +575,7 @@ class InteractiveTuiPrompt {
|
|
|
365
575
|
_cleanupInput() {
|
|
366
576
|
if (this._renderTimer) { clearTimeout(this._renderTimer); this._renderTimer = null; }
|
|
367
577
|
if (this._keyHandler) { input.removeListener('keypress', this._keyHandler); this._keyHandler = null; }
|
|
368
|
-
try { if (input.isTTY) input.setRawMode(false); } catch {}
|
|
578
|
+
try { if (input.isTTY) input.setRawMode(false); } catch { }
|
|
369
579
|
}
|
|
370
580
|
|
|
371
581
|
async readLine() {
|
|
@@ -456,68 +666,84 @@ class InteractiveTuiPrompt {
|
|
|
456
666
|
render() {
|
|
457
667
|
if (this.isDisposed || !this.screenBuffer) return;
|
|
458
668
|
const termCols = Math.max(20, process.stdout.columns || 80);
|
|
459
|
-
const
|
|
669
|
+
const {
|
|
670
|
+
termRows,
|
|
671
|
+
viewportStartRow,
|
|
672
|
+
viewportHeight,
|
|
673
|
+
statusBarRow,
|
|
674
|
+
promptRow,
|
|
675
|
+
separatorRow,
|
|
676
|
+
} = getTuiViewportLayout(process.stdout.rows || 24);
|
|
460
677
|
|
|
461
678
|
this.screenBuffer.resize(termCols, termRows);
|
|
462
679
|
this.screenBuffer.clear(C.bgDark);
|
|
463
680
|
|
|
464
|
-
|
|
681
|
+
|
|
465
682
|
const shortModel = (this.modelName || 'gemma-4-e4b').replace(/^.*\//, '');
|
|
466
683
|
const modelLabel = `${C.gold}✦ ${shortModel}${C.reset}`;
|
|
467
684
|
const mcpCount = this.agent?.mcpManager?.servers?.size || 0;
|
|
468
685
|
const mcpLabel = mcpCount > 0 ? `${C.emerald}● ${mcpCount} MCP${C.reset}` : `${C.slate}○ 0 MCP${C.reset}`;
|
|
469
686
|
|
|
470
|
-
const
|
|
471
|
-
const
|
|
687
|
+
const activeTheme = getTheme ? getTheme() : null;
|
|
688
|
+
const themeName = activeTheme?.label || 'Sakura';
|
|
689
|
+
const headerLeft = ` ${C.bold}${C.sakura}◆ KIKU AGENT${C.reset} ${C.slate}v${PACKAGE_VERSION}${C.reset}`;
|
|
690
|
+
const headerRight = `${modelLabel} ${C.slate}│${C.reset} ${mcpLabel} ${C.slate}│${C.reset} ${C.roseGlow}◆ ${themeName}${C.reset} `;
|
|
472
691
|
const safeCols = Math.max(1, termCols - 1);
|
|
473
692
|
const headerPad = Math.max(1, safeCols - getStringDisplayWidth(headerLeft) - getStringDisplayWidth(headerRight));
|
|
474
693
|
this.screenBuffer.setRow(0, `${headerLeft}${' '.repeat(headerPad)}${headerRight}`, C.bgPanelHeader);
|
|
475
694
|
|
|
476
695
|
|
|
477
|
-
const vpStartRow = 1;
|
|
478
|
-
const vpHeight = Math.max(1, termRows - 4);
|
|
479
696
|
const totalLines = this.viewportLines.length;
|
|
480
|
-
const maxOffset = Math.max(0, totalLines -
|
|
697
|
+
const maxOffset = Math.max(0, totalLines - viewportHeight);
|
|
481
698
|
const currentOffset = Math.min(this.scrollOffset, maxOffset);
|
|
482
|
-
const startIndex = Math.max(0, totalLines -
|
|
483
|
-
const visibleLines = this.viewportLines.slice(startIndex, startIndex +
|
|
699
|
+
const startIndex = Math.max(0, totalLines - viewportHeight - currentOffset);
|
|
700
|
+
const visibleLines = this.viewportLines.slice(startIndex, startIndex + viewportHeight);
|
|
484
701
|
|
|
485
|
-
for (let r = 0; r <
|
|
702
|
+
for (let r = 0; r < viewportHeight; r++) {
|
|
486
703
|
const lineContent = visibleLines[r] || '';
|
|
487
|
-
this.screenBuffer.setRow(
|
|
704
|
+
this.screenBuffer.setRow(viewportStartRow + r, lineContent, C.bgDark);
|
|
488
705
|
}
|
|
489
706
|
|
|
490
|
-
|
|
491
|
-
const statusBarRow = termRows - 4;
|
|
492
707
|
const musicStatus = audioPlayer.getStatus();
|
|
493
708
|
let statusStyled = '';
|
|
494
709
|
|
|
495
710
|
if (isProcessing) {
|
|
496
711
|
const frame = spinnerFrames[Math.floor(Date.now() / 70) % spinnerFrames.length];
|
|
497
|
-
const stateLabel = stripAnsi(this.agentState || 'Processing
|
|
498
|
-
const elapsed = this.agentState
|
|
499
|
-
? ` ${((Date.now() -
|
|
712
|
+
const stateLabel = stripAnsi(this.agentState || 'Processing…');
|
|
713
|
+
const elapsed = this.agentState && this.agentStateTime
|
|
714
|
+
? ` ${((Date.now() - this.agentStateTime) / 1000).toFixed(1)}s`
|
|
500
715
|
: '';
|
|
501
716
|
const queueLabel = queryQueue.length > 0 ? ` Queue: ${queryQueue.length}` : '';
|
|
502
717
|
const activeLabel = activeQueryNumber == null ? '' : `${C.bold}Q${activeQueryNumber}${C.reset}${C.cyberCyan} • `;
|
|
503
|
-
|
|
718
|
+
const statusLeft = ` ${C.cyberCyan}${frame} ${activeLabel}${stateLabel}${elapsed}${C.reset}${C.slate}${queueLabel}${C.reset}`;
|
|
719
|
+
if (currentOffset > 0) {
|
|
720
|
+
const statusRight = `${C.amber}${C.bold}↓ ${currentOffset} more${C.reset} ${C.slate}below (press [End])${C.reset} `;
|
|
721
|
+
const pad = Math.max(1, safeCols - getStringDisplayWidth(statusLeft) - getStringDisplayWidth(statusRight));
|
|
722
|
+
statusStyled = `${statusLeft}${' '.repeat(pad)}${statusRight}`;
|
|
723
|
+
} else {
|
|
724
|
+
statusStyled = statusLeft;
|
|
725
|
+
}
|
|
726
|
+
} else if (currentOffset > 0) {
|
|
727
|
+
const posInfo = startIndex > 0 ? ` ${C.slate}• ↑ ${startIndex} above${C.reset}` : '';
|
|
728
|
+
const statusLeft = ` ${C.amber}${C.bold}↓ ${currentOffset} more${C.reset} ${C.slate}below${C.reset}${posInfo}`;
|
|
729
|
+
const statusRight = `${C.slate}press ${C.white}${C.bold}[End]${C.reset}${C.slate} to return to bottom${C.reset} `;
|
|
730
|
+
const pad = Math.max(1, safeCols - getStringDisplayWidth(statusLeft) - getStringDisplayWidth(statusRight));
|
|
731
|
+
statusStyled = `${statusLeft}${' '.repeat(pad)}${statusRight}`;
|
|
504
732
|
} else if (musicStatus) {
|
|
505
733
|
statusStyled = ` ${C.cyberCyan}🎵 ${musicStatus}${C.reset}`;
|
|
506
|
-
} else if (currentOffset > 0) {
|
|
507
|
-
statusStyled = ` ${C.amber}[↑ SCROLLED +${currentOffset} lines — press [End] to return to live stream]${C.reset}`;
|
|
508
734
|
} else {
|
|
509
|
-
statusStyled = ` ${C.emerald}
|
|
735
|
+
statusStyled = ` ${C.emerald}● READY${C.reset}`;
|
|
510
736
|
}
|
|
511
737
|
|
|
512
738
|
this.screenBuffer.setRow(statusBarRow, statusStyled, C.bgPanelHeader);
|
|
513
739
|
|
|
514
|
-
|
|
740
|
+
|
|
515
741
|
if (this.popoverActive && this.popoverItems.length > 0) {
|
|
516
|
-
const maxItems = Math.min(5, Math.max(1,
|
|
742
|
+
const maxItems = Math.min(5, Math.max(1, viewportHeight - 1));
|
|
517
743
|
const startIdx = Math.max(0, Math.min(this.popoverIndex - 2, this.popoverItems.length - maxItems));
|
|
518
744
|
const visibleItems = this.popoverItems.slice(startIdx, startIdx + maxItems);
|
|
519
745
|
const popoverHeight = visibleItems.length + 1;
|
|
520
|
-
const popoverStartRow = Math.max(
|
|
746
|
+
const popoverStartRow = Math.max(viewportStartRow, statusBarRow - popoverHeight);
|
|
521
747
|
|
|
522
748
|
this.screenBuffer.setRow(
|
|
523
749
|
popoverStartRow,
|
|
@@ -540,11 +766,10 @@ class InteractiveTuiPrompt {
|
|
|
540
766
|
}
|
|
541
767
|
}
|
|
542
768
|
|
|
543
|
-
|
|
544
|
-
const promptRow = termRows - 2;
|
|
769
|
+
|
|
545
770
|
let promptStyled = `${C.bold}${C.sakura}${this.promptText}${C.reset}`;
|
|
546
771
|
const promptVisualWidth = getStringDisplayWidth(this.promptText);
|
|
547
|
-
const maxLineWidth = Math.max(1, termCols - promptVisualWidth -
|
|
772
|
+
const maxLineWidth = Math.max(1, termCols - promptVisualWidth - 2);
|
|
548
773
|
|
|
549
774
|
const lineVisualWidth = getStringDisplayWidth(this.line);
|
|
550
775
|
const { displayLine, cursorVisualOffset } = sliceLineForDisplay(this.line, this.cursor, maxLineWidth);
|
|
@@ -563,22 +788,35 @@ class InteractiveTuiPrompt {
|
|
|
563
788
|
trailingStyled = `${C.dim}${C.slate}${placeholder}${C.reset}`;
|
|
564
789
|
}
|
|
565
790
|
|
|
566
|
-
const rowStyled = `
|
|
791
|
+
const rowStyled = ` ${promptStyled}${C.white}${displayLine}${trailingStyled}`;
|
|
567
792
|
this.screenBuffer.setRow(promptRow, rowStyled, C.bgPanel);
|
|
568
793
|
|
|
569
|
-
this.screenBuffer.setRow(
|
|
794
|
+
this.screenBuffer.setRow(separatorRow, '─'.repeat(termCols), C.bgDark);
|
|
570
795
|
|
|
571
|
-
const cursorCol =
|
|
796
|
+
const cursorCol = 2 + promptVisualWidth + cursorVisualOffset;
|
|
572
797
|
this.screenBuffer.setCursor(promptRow, cursorCol, true);
|
|
573
798
|
this.screenBuffer.render();
|
|
574
799
|
}
|
|
575
800
|
|
|
576
801
|
handleKeypress(str, key) {
|
|
577
|
-
if (!key) return;
|
|
802
|
+
if (!key && !str) return;
|
|
578
803
|
|
|
579
|
-
if (key
|
|
580
|
-
if (
|
|
581
|
-
|
|
804
|
+
if ((key?.ctrl && key?.name === 'c') || str === '\x03') {
|
|
805
|
+
if (isProcessing) {
|
|
806
|
+
if (this.onCancel) this.onCancel();
|
|
807
|
+
else if (this.onSigInt) this.onSigInt();
|
|
808
|
+
this.addViewportLines(` ${C.amber}[✓ Cancelled by user]${C.reset}`);
|
|
809
|
+
this.agentState = '';
|
|
810
|
+
this.render();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (this.line.length > 0) {
|
|
814
|
+
this.line = '';
|
|
815
|
+
this.cursor = 0;
|
|
816
|
+
this.updateState();
|
|
817
|
+
this.render();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
582
820
|
const now = Date.now();
|
|
583
821
|
if (!this._lastCtrlCTime || now - this._lastCtrlCTime > 2000) {
|
|
584
822
|
this._lastCtrlCTime = now;
|
|
@@ -589,49 +827,88 @@ class InteractiveTuiPrompt {
|
|
|
589
827
|
return;
|
|
590
828
|
}
|
|
591
829
|
|
|
592
|
-
if (key
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
830
|
+
if (key?.name === 'escape' || str === '\x1b') {
|
|
831
|
+
if (this.popoverActive) {
|
|
832
|
+
this.popoverActive = false;
|
|
833
|
+
this.popoverItems = [];
|
|
834
|
+
this.ghostText = '';
|
|
835
|
+
this.render();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (this.scrollOffset > 0) {
|
|
839
|
+
this.scrollOffset = 0;
|
|
840
|
+
this.render();
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (isProcessing) {
|
|
844
|
+
if (this.onCancel) this.onCancel();
|
|
845
|
+
this.addViewportLines(` ${C.amber}[✓ Cancelled by user]${C.reset}`);
|
|
846
|
+
this.agentState = '';
|
|
847
|
+
this.render();
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
598
850
|
return;
|
|
599
851
|
}
|
|
600
852
|
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
853
|
+
if (
|
|
854
|
+
str && (
|
|
855
|
+
str.startsWith('\x1b') ||
|
|
856
|
+
str.includes('\x1b') ||
|
|
857
|
+
str.includes('[<') ||
|
|
858
|
+
str.includes('[M') ||
|
|
859
|
+
/^<[0-9;]*[a-zA-Z]?$/.test(str) ||
|
|
860
|
+
/^[0-9;]+[a-zA-Z]$/.test(str) ||
|
|
861
|
+
/^[0-9;]{2,}$/.test(str) ||
|
|
862
|
+
/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(str)
|
|
863
|
+
)
|
|
864
|
+
) {
|
|
865
|
+
if (str.includes('[<64;') || str.includes('[64;') || str.includes('<64;') || (key?.name === 'up' && key?.shift)) {
|
|
866
|
+
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
867
|
+
const maxOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
868
|
+
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + 3);
|
|
869
|
+
this.render();
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (str.includes('[<65;') || str.includes('[65;') || str.includes('<65;') || (key?.name === 'down' && key?.shift)) {
|
|
873
|
+
if (this.scrollOffset > 0) {
|
|
874
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 3);
|
|
875
|
+
this.render();
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (str !== '\r' && str !== '\n' && str !== '\t' && str !== '\x08' && str !== '\x7f') {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (key?.name === 'pageup') {
|
|
885
|
+
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
886
|
+
const maxOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
887
|
+
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + Math.max(1, viewportHeight - 2));
|
|
605
888
|
this.render();
|
|
606
889
|
return;
|
|
607
890
|
}
|
|
608
891
|
|
|
609
|
-
if (key
|
|
892
|
+
if (key?.name === 'pagedown') {
|
|
893
|
+
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
894
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - Math.max(1, viewportHeight - 2));
|
|
610
895
|
this.render();
|
|
611
896
|
return;
|
|
612
897
|
}
|
|
613
898
|
|
|
614
|
-
if (key
|
|
615
|
-
|
|
616
|
-
this.popoverActive = false;
|
|
617
|
-
this.popoverItems = [];
|
|
618
|
-
this.ghostText = '';
|
|
619
|
-
this.render();
|
|
620
|
-
} else if (this.scrollOffset > 0) {
|
|
621
|
-
this.scrollOffset = 0;
|
|
622
|
-
this.render();
|
|
623
|
-
}
|
|
899
|
+
if (key?.ctrl && key?.name === 'l') {
|
|
900
|
+
this.render();
|
|
624
901
|
return;
|
|
625
902
|
}
|
|
626
903
|
|
|
627
|
-
if (key
|
|
904
|
+
if (key?.name === 'f2') {
|
|
628
905
|
this.finish('/details');
|
|
629
906
|
return;
|
|
630
907
|
}
|
|
631
908
|
|
|
632
|
-
if (key
|
|
909
|
+
if (key?.name === 'tab' || str === '\t') {
|
|
633
910
|
if (this.popoverActive && this.popoverItems.length > 0) {
|
|
634
|
-
const item = this.popoverItems[this.popoverIndex];
|
|
911
|
+
const item = this.popoverItems[this.popoverIndex] || this.popoverItems[0];
|
|
635
912
|
if (item) {
|
|
636
913
|
this.line = item.insert || item.name;
|
|
637
914
|
this.cursor = item.newCursor !== undefined ? item.newCursor : this.line.length;
|
|
@@ -649,25 +926,24 @@ class InteractiveTuiPrompt {
|
|
|
649
926
|
return;
|
|
650
927
|
}
|
|
651
928
|
|
|
652
|
-
if (key
|
|
929
|
+
if (key?.name === 'return' || key?.name === 'enter' || str === '\r' || str === '\n') {
|
|
930
|
+
let textToSubmit = this.line;
|
|
653
931
|
if (this.popoverActive && this.popoverItems.length > 0) {
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
const
|
|
657
|
-
if (
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
this.render();
|
|
662
|
-
return;
|
|
932
|
+
const selected = this.popoverItems[this.popoverIndex] || this.popoverItems[0];
|
|
933
|
+
if (selected) {
|
|
934
|
+
const target = (selected.insert || selected.name || '').trim();
|
|
935
|
+
if (this.line.startsWith('/') && !this.line.includes(' ')) {
|
|
936
|
+
textToSubmit = target;
|
|
937
|
+
} else if (this.line.startsWith('/') && this.line.length < target.length && target.startsWith(this.line.trim())) {
|
|
938
|
+
textToSubmit = target;
|
|
663
939
|
}
|
|
664
|
-
this.line = insertVal;
|
|
665
|
-
this.finish(this.line);
|
|
666
|
-
return;
|
|
667
940
|
}
|
|
668
941
|
}
|
|
942
|
+
this.popoverActive = false;
|
|
943
|
+
this.popoverItems = [];
|
|
944
|
+
this.ghostText = '';
|
|
669
945
|
this.scrollOffset = 0;
|
|
670
|
-
this.finish(
|
|
946
|
+
this.finish(textToSubmit);
|
|
671
947
|
return;
|
|
672
948
|
}
|
|
673
949
|
|
|
@@ -687,22 +963,21 @@ class InteractiveTuiPrompt {
|
|
|
687
963
|
this.render();
|
|
688
964
|
return;
|
|
689
965
|
}
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
const maxOffset = Math.max(0, this.viewportLines.length - vpHeight);
|
|
966
|
+
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
967
|
+
const maxOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
693
968
|
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + 1);
|
|
694
969
|
this.render();
|
|
695
970
|
return;
|
|
696
971
|
}
|
|
697
972
|
|
|
698
|
-
if (key
|
|
973
|
+
if (key?.name === 'down' || (key?.ctrl && key?.name === 'n')) {
|
|
699
974
|
if (this.popoverActive && this.popoverItems.length > 0) {
|
|
700
975
|
this.popoverIndex = (this.popoverIndex + 1) % this.popoverItems.length;
|
|
701
976
|
this.updateState();
|
|
702
977
|
this.render();
|
|
703
978
|
return;
|
|
704
979
|
}
|
|
705
|
-
if (key
|
|
980
|
+
if (key?.ctrl && this.history.length > 0 && this.historyIndex !== -1) {
|
|
706
981
|
if (this.historyIndex < this.history.length - 1) {
|
|
707
982
|
this.historyIndex++;
|
|
708
983
|
this.line = this.history[this.historyIndex] || '';
|
|
@@ -722,11 +997,10 @@ class InteractiveTuiPrompt {
|
|
|
722
997
|
return;
|
|
723
998
|
}
|
|
724
999
|
|
|
725
|
-
if (key
|
|
1000
|
+
if (key?.name === 'home' || (key?.ctrl && key?.name === 'a')) {
|
|
726
1001
|
if (this.line === '') {
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
this.scrollOffset = Math.max(0, this.viewportLines.length - vpHeight);
|
|
1002
|
+
const { viewportHeight } = getTuiViewportLayout(process.stdout.rows || 24);
|
|
1003
|
+
this.scrollOffset = Math.max(0, this.viewportLines.length - viewportHeight);
|
|
730
1004
|
this.render();
|
|
731
1005
|
} else {
|
|
732
1006
|
this.cursor = 0;
|
|
@@ -735,7 +1009,7 @@ class InteractiveTuiPrompt {
|
|
|
735
1009
|
return;
|
|
736
1010
|
}
|
|
737
1011
|
|
|
738
|
-
if (key
|
|
1012
|
+
if (key?.name === 'end' || (key?.ctrl && key?.name === 'e')) {
|
|
739
1013
|
if (this.line === '') {
|
|
740
1014
|
this.scrollOffset = 0;
|
|
741
1015
|
this.render();
|
|
@@ -746,18 +1020,22 @@ class InteractiveTuiPrompt {
|
|
|
746
1020
|
return;
|
|
747
1021
|
}
|
|
748
1022
|
|
|
749
|
-
if (key
|
|
750
|
-
if (key
|
|
1023
|
+
if (key?.name === 'right') { if (this.cursor < this.line.length) { this.cursor = nextGraphemeBoundary(this.line, this.cursor); this.render(); } return; }
|
|
1024
|
+
if (key?.name === 'left') { if (this.cursor > 0) { this.cursor = previousGraphemeBoundary(this.line, this.cursor); this.render(); } return; }
|
|
751
1025
|
|
|
752
|
-
if (key
|
|
753
|
-
if (key
|
|
1026
|
+
if (key?.name === 'backspace' || str === '\x08' || str === '\x7f') { if (this.cursor > 0) { const previous = previousGraphemeBoundary(this.line, this.cursor); this.line = this.line.slice(0, previous) + this.line.slice(this.cursor); this.cursor = previous; this.updateState(); this.render(); } return; }
|
|
1027
|
+
if (key?.name === 'delete') { if (this.cursor < this.line.length) { const next = nextGraphemeBoundary(this.line, this.cursor); this.line = this.line.slice(0, this.cursor) + this.line.slice(next); this.updateState(); this.render(); } return; }
|
|
754
1028
|
|
|
755
|
-
if (str && !key
|
|
1029
|
+
if (str && !key?.ctrl && !key?.meta) {
|
|
756
1030
|
if (
|
|
757
1031
|
str.startsWith('\x1b') ||
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1032
|
+
str.includes('\x1b') ||
|
|
1033
|
+
str.includes('[<') ||
|
|
1034
|
+
str.includes('[M') ||
|
|
1035
|
+
/[\x00-\x1f\x7f]/.test(str) ||
|
|
1036
|
+
/^<[0-9;]*[a-zA-Z]?$/.test(str) ||
|
|
1037
|
+
/^[0-9;]+[a-zA-Z]$/.test(str) ||
|
|
1038
|
+
/^[0-9;]{2,}$/.test(str)
|
|
761
1039
|
) return;
|
|
762
1040
|
this.line = this.line.slice(0, this.cursor) + str + this.line.slice(this.cursor);
|
|
763
1041
|
this.cursor += str.length;
|
|
@@ -815,10 +1093,19 @@ function getSuggestionsForLine(line, cursor = (line || '').length, agent = null)
|
|
|
815
1093
|
|
|
816
1094
|
if (parts.length > 1 || (parts.length === 1 && line.includes(' '))) {
|
|
817
1095
|
const subs = SUB_COMMANDS[baseCmd] || [];
|
|
1096
|
+
const details = getCommandDetails(baseCmd);
|
|
818
1097
|
const query = parts.slice(1).join(' ').toLowerCase();
|
|
819
1098
|
return subs
|
|
820
1099
|
.filter(s => s.toLowerCase().includes(query))
|
|
821
|
-
.map(s =>
|
|
1100
|
+
.map(s => {
|
|
1101
|
+
const subKey = s.split(/\s+/)[0].toLowerCase();
|
|
1102
|
+
const matchedDetail = details?.subcommands?.find(sc => {
|
|
1103
|
+
const scKey = sc.cmd.split(/\s+/)[0].toLowerCase();
|
|
1104
|
+
return scKey === subKey || sc.cmd.toLowerCase().includes(subKey);
|
|
1105
|
+
});
|
|
1106
|
+
const desc = matchedDetail?.desc || '';
|
|
1107
|
+
return { name: `${baseCmd} ${s}`, desc, insert: `${baseCmd} ${s} ` };
|
|
1108
|
+
});
|
|
822
1109
|
}
|
|
823
1110
|
|
|
824
1111
|
return allCmds
|
|
@@ -826,589 +1113,916 @@ function getSuggestionsForLine(line, cursor = (line || '').length, agent = null)
|
|
|
826
1113
|
.filter(c => c.score > 0)
|
|
827
1114
|
.sort((a, b) => b.score - a.score);
|
|
828
1115
|
}
|
|
829
|
-
|
|
830
1116
|
async function executeQueryWithUI(agent, prompt, isThinkingVisible = false, signal = null, queryNumber = null) {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1117
|
+
const queryStartedAt = Date.now();
|
|
1118
|
+
let spinnerInterval = null;
|
|
1119
|
+
let hasPrintedResponseHeader = false;
|
|
1120
|
+
let responsePanelStarted = false;
|
|
1121
|
+
let toolSectionStarted = false;
|
|
1122
|
+
let responseLineBuffer = '';
|
|
1123
|
+
let pendingTableHeader = null;
|
|
1124
|
+
let activeTableHeaders = null;
|
|
1125
|
+
let tableRowNumber = 0;
|
|
1126
|
+
const boxWidth = getFullWidth();
|
|
1127
|
+
const bg = C.bgPanel;
|
|
1128
|
+
const bgHeader = C.bgPanelHeader;
|
|
1129
|
+
const colorizeResponseText = (formatted) => (
|
|
1130
|
+
`${C.white}${formatted.replace(/\x1b\[0m/g, C.reset + C.white)}${C.reset}`
|
|
1131
|
+
);
|
|
1132
|
+
const queryTag = queryNumber == null ? '' : `Q${queryNumber}`;
|
|
1133
|
+
const answerTitle = queryTag
|
|
1134
|
+
? `Answer • ${queryTag} — ${prompt}`
|
|
1135
|
+
: 'Answer';
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
let jsonSuppressDepth = 0;
|
|
1140
|
+
let jsonSuppressActive = false;
|
|
1141
|
+
let fencedJsonBlock = false;
|
|
1142
|
+
let suppressedLineCount = 0;
|
|
1143
|
+
|
|
1144
|
+
const _RAW_JSON_FIELD_RE = /["'](?:_meta|protocol|requestedTool|authoritative|tool_call_id|validationWarnings|cache|duplicate_call_in_query)["']\s*:/;
|
|
1145
|
+
const _ACTION_INPUT_RE = /^Action\s*:\s*[a-zA-Z0-9_]+\s*$/;
|
|
1146
|
+
const _ACTION_INPUT_BODY_RE = /^Action Input\s*:\s*\{/;
|
|
1147
|
+
|
|
1148
|
+
const isRawJsonPayloadLine = (line) => {
|
|
1149
|
+
const trimmed = line.trim();
|
|
1150
|
+
if (/^```json\s*$/i.test(trimmed)) return 'fence_start';
|
|
1151
|
+
if (fencedJsonBlock && /^```\s*$/.test(trimmed)) return 'fence_end';
|
|
1152
|
+
if (_RAW_JSON_FIELD_RE.test(trimmed)) return 'json_field';
|
|
1153
|
+
if (_ACTION_INPUT_RE.test(trimmed) || _ACTION_INPUT_BODY_RE.test(trimmed)) return 'action_pattern';
|
|
1154
|
+
if (jsonSuppressActive) {
|
|
1155
|
+
if (/^[{}\[\],]*$/.test(trimmed)) return 'json_structure';
|
|
1156
|
+
if (/^["'][a-zA-Z_]+["']\s*:/.test(trimmed)) return 'json_field';
|
|
1157
|
+
if (/^(?:\d+|true|false|null|"[^"]*")\s*,?\s*$/.test(trimmed)) return 'json_value';
|
|
1158
|
+
}
|
|
1159
|
+
return null;
|
|
1160
|
+
};
|
|
856
1161
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
const wrapSafeLine = expandMarkdownLinks(line);
|
|
860
|
-
const formatted = formatTerminalMarkdown(wrapSafeLine);
|
|
861
|
-
const maxWidth = getFullWidth() - 4;
|
|
862
|
-
const visualLen = getStringDisplayWidth(formatted);
|
|
863
|
-
if (visualLen > maxWidth) {
|
|
864
|
-
for (const wl of wrapText(wrapSafeLine, maxWidth)) {
|
|
865
|
-
printSolidBannerRow(` ${colorizeResponseText(formatTerminalMarkdown(wl))}`, boxWidth, C.bgPanel);
|
|
866
|
-
}
|
|
867
|
-
} else {
|
|
868
|
-
printSolidBannerRow(` ${colorizeResponseText(formatted)}`, boxWidth, C.bgPanel);
|
|
869
|
-
}
|
|
870
|
-
};
|
|
1162
|
+
const filterResponseLine = (line) => {
|
|
1163
|
+
const detection = isRawJsonPayloadLine(line);
|
|
871
1164
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1165
|
+
if (detection === 'fence_start') {
|
|
1166
|
+
fencedJsonBlock = true;
|
|
1167
|
+
jsonSuppressActive = true;
|
|
1168
|
+
suppressedLineCount = 0;
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
if (detection === 'fence_end') {
|
|
1172
|
+
fencedJsonBlock = false;
|
|
1173
|
+
jsonSuppressActive = false;
|
|
1174
|
+
if (suppressedLineCount > 0) {
|
|
1175
|
+
printPlainResponseLine(`${C.dim}${C.slate}[tool data filtered — ${suppressedLineCount} lines]${C.reset}`);
|
|
1176
|
+
}
|
|
1177
|
+
suppressedLineCount = 0;
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
if (fencedJsonBlock) {
|
|
1181
|
+
suppressedLineCount++;
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
888
1184
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1185
|
+
if (detection) {
|
|
1186
|
+
if (!jsonSuppressActive) {
|
|
1187
|
+
jsonSuppressActive = true;
|
|
1188
|
+
jsonSuppressDepth = 0;
|
|
1189
|
+
suppressedLineCount = 0;
|
|
1190
|
+
}
|
|
1191
|
+
const trimmed = line.trim();
|
|
1192
|
+
for (const ch of trimmed) {
|
|
1193
|
+
if (ch === '{' || ch === '[') jsonSuppressDepth++;
|
|
1194
|
+
else if (ch === '}' || ch === ']') jsonSuppressDepth--;
|
|
1195
|
+
}
|
|
1196
|
+
suppressedLineCount++;
|
|
1197
|
+
if (jsonSuppressDepth <= 0 && suppressedLineCount > 1) {
|
|
1198
|
+
jsonSuppressActive = false;
|
|
1199
|
+
if (suppressedLineCount > 2) {
|
|
1200
|
+
printPlainResponseLine(`${C.dim}${C.slate}[tool data filtered — ${suppressedLineCount} lines]${C.reset}`);
|
|
1201
|
+
}
|
|
1202
|
+
suppressedLineCount = 0;
|
|
1203
|
+
}
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
894
1206
|
|
|
895
|
-
|
|
896
|
-
|
|
1207
|
+
if (jsonSuppressActive) {
|
|
1208
|
+
jsonSuppressActive = false;
|
|
1209
|
+
jsonSuppressDepth = 0;
|
|
1210
|
+
if (suppressedLineCount > 2) {
|
|
1211
|
+
printPlainResponseLine(`${C.dim}${C.slate}[tool data filtered — ${suppressedLineCount} lines]${C.reset}`);
|
|
1212
|
+
}
|
|
1213
|
+
suppressedLineCount = 0;
|
|
1214
|
+
}
|
|
897
1215
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1216
|
+
return true;
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
const ensureResponsePanel = () => {
|
|
1220
|
+
if (responsePanelStarted) return;
|
|
1221
|
+
console.log('');
|
|
1222
|
+
printSolidBannerRow(` ${C.bold}${C.roseGlow}${answerTitle}${C.reset}`, boxWidth, C.bgPanelHeader);
|
|
1223
|
+
responsePanelStarted = true;
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
const printPlainResponseLine = (line) => {
|
|
1227
|
+
ensureResponsePanel();
|
|
1228
|
+
const wrapSafeLine = expandMarkdownLinks(line);
|
|
1229
|
+
const formatted = formatTerminalMarkdown(wrapSafeLine);
|
|
1230
|
+
const maxWidth = getFullWidth() - 4;
|
|
1231
|
+
const visualLen = getStringDisplayWidth(formatted);
|
|
1232
|
+
if (visualLen > maxWidth) {
|
|
1233
|
+
for (const wl of wrapText(wrapSafeLine, maxWidth)) {
|
|
1234
|
+
printSolidBannerRow(` ${colorizeResponseText(formatTerminalMarkdown(wl))}`, boxWidth, C.bgPanel);
|
|
1235
|
+
}
|
|
1236
|
+
} else {
|
|
1237
|
+
printSolidBannerRow(` ${colorizeResponseText(formatted)}`, boxWidth, C.bgPanel);
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
const printTableRecord = (cells) => {
|
|
1242
|
+
ensureResponsePanel();
|
|
1243
|
+
tableRowNumber++;
|
|
1244
|
+
printSolidBannerRow(` ${C.bold}${C.cyberCyan}Item ${tableRowNumber}${C.reset}`, boxWidth, C.bgPanelHeader);
|
|
1245
|
+
|
|
1246
|
+
const maxWidth = Math.max(16, getFullWidth() - 8);
|
|
1247
|
+
for (let index = 0; index < activeTableHeaders.length; index++) {
|
|
1248
|
+
const header = activeTableHeaders[index] || `Column ${index + 1}`;
|
|
1249
|
+
const value = expandMarkdownLinks(cells[index] || '—');
|
|
1250
|
+
const prefix = `${header}: `;
|
|
1251
|
+
const wrapped = wrapText(prefix + value, maxWidth);
|
|
1252
|
+
for (const line of wrapped) {
|
|
1253
|
+
printSolidBannerRow(` ${colorizeResponseText(formatTerminalMarkdown(line))}`, boxWidth, C.bgPanel);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
907
1257
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
activeTableHeaders = null;
|
|
914
|
-
tableRowNumber = 0;
|
|
915
|
-
}
|
|
1258
|
+
const flushPendingTableHeader = () => {
|
|
1259
|
+
if (!pendingTableHeader) return;
|
|
1260
|
+
printPlainResponseLine(`| ${pendingTableHeader.join(' | ')} |`);
|
|
1261
|
+
pendingTableHeader = null;
|
|
1262
|
+
};
|
|
916
1263
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
1264
|
+
const printResponseLine = (line) => {
|
|
1265
|
+
if (!filterResponseLine(line)) return;
|
|
921
1266
|
|
|
922
|
-
|
|
923
|
-
};
|
|
1267
|
+
const cells = parseMarkdownTableRow(line);
|
|
924
1268
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
if (isFinal) flushPendingTableHeader();
|
|
937
|
-
};
|
|
1269
|
+
if (pendingTableHeader) {
|
|
1270
|
+
if (cells && cells.length === pendingTableHeader.length && isMarkdownTableSeparator(cells)) {
|
|
1271
|
+
activeTableHeaders = pendingTableHeader;
|
|
1272
|
+
pendingTableHeader = null;
|
|
1273
|
+
tableRowNumber = 0;
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
flushPendingTableHeader();
|
|
1277
|
+
}
|
|
938
1278
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1279
|
+
if (activeTableHeaders) {
|
|
1280
|
+
if (cells && cells.length === activeTableHeaders.length && !isMarkdownTableSeparator(cells)) {
|
|
1281
|
+
printTableRecord(cells);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
activeTableHeaders = null;
|
|
1285
|
+
tableRowNumber = 0;
|
|
1286
|
+
}
|
|
945
1287
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1288
|
+
if (cells && !isMarkdownTableSeparator(cells)) {
|
|
1289
|
+
pendingTableHeader = cells;
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
950
1292
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1293
|
+
printPlainResponseLine(line);
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const flushResponseBuffer = (isFinal = false) => {
|
|
1297
|
+
if (responseLineBuffer.length > 0) {
|
|
1298
|
+
if (isFinal) {
|
|
1299
|
+
for (const line of responseLineBuffer.split('\n')) { if (line.length > 0) printResponseLine(line); }
|
|
1300
|
+
responseLineBuffer = '';
|
|
1301
|
+
} else if (responseLineBuffer.includes('\n')) {
|
|
1302
|
+
const lines = responseLineBuffer.split('\n');
|
|
1303
|
+
responseLineBuffer = lines.pop();
|
|
1304
|
+
for (const line of lines) printResponseLine(line);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (isFinal) {
|
|
1308
|
+
if (jsonSuppressActive && suppressedLineCount > 2) {
|
|
1309
|
+
printPlainResponseLine(`${C.dim}${C.slate}[tool data filtered — ${suppressedLineCount} lines]${C.reset}`);
|
|
1310
|
+
}
|
|
1311
|
+
jsonSuppressActive = false;
|
|
1312
|
+
jsonSuppressDepth = 0;
|
|
1313
|
+
suppressedLineCount = 0;
|
|
1314
|
+
fencedJsonBlock = false;
|
|
1315
|
+
flushPendingTableHeader();
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
957
1318
|
|
|
958
|
-
|
|
1319
|
+
const startSpinner = (label) => {
|
|
1320
|
+
if (globalTuiPrompt) {
|
|
1321
|
+
globalTuiPrompt.agentState = label;
|
|
1322
|
+
globalTuiPrompt.agentStateTime = Date.now();
|
|
1323
|
+
globalTuiPrompt.render();
|
|
1324
|
+
}
|
|
1325
|
+
if (!spinnerInterval) {
|
|
1326
|
+
spinnerInterval = setInterval(() => {
|
|
1327
|
+
if (globalTuiPrompt && isProcessing) {
|
|
1328
|
+
globalTuiPrompt.render();
|
|
1329
|
+
}
|
|
1330
|
+
}, 80);
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
959
1333
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
onReasoningStart: () => {
|
|
965
|
-
stopSpinner();
|
|
966
|
-
thinkingStartTime = Date.now();
|
|
967
|
-
thinkingBuffer = '';
|
|
968
|
-
if (!thinkingShown) {
|
|
969
|
-
console.log('');
|
|
970
|
-
const icon = thinkingFrames[Math.floor(Date.now() / 200) % thinkingFrames.length];
|
|
971
|
-
printSolidBannerRow(` ${C.lavender}${C.bold}${icon} Thinking${queryTag ? ` • ${queryTag}` : ''}${C.reset}`, boxWidth, bgHeader);
|
|
972
|
-
}
|
|
973
|
-
},
|
|
974
|
-
onReasoningDelta: (chunk) => {
|
|
975
|
-
accumulatedThinking += chunk;
|
|
976
|
-
thinkingBuffer += chunk;
|
|
977
|
-
if (!thinkingShown) {
|
|
978
|
-
const lines = thinkingBuffer.split('\n');
|
|
979
|
-
thinkingBuffer = lines.pop();
|
|
980
|
-
for (const line of lines) {
|
|
981
|
-
if (line.trim()) {
|
|
982
|
-
for (const w of wrapText(cleanThinkingLine(line), boxWidth - 6)) {
|
|
983
|
-
printSolidBannerRow(` ${C.dim}${C.slate}${w}${C.reset}`, boxWidth, bg);
|
|
984
|
-
}
|
|
1334
|
+
const stopSpinner = () => {
|
|
1335
|
+
if (spinnerInterval) {
|
|
1336
|
+
clearInterval(spinnerInterval);
|
|
1337
|
+
spinnerInterval = null;
|
|
985
1338
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
onReasoningEnd: () => {
|
|
990
|
-
const elapsed = ((Date.now() - thinkingStartTime) / 1000).toFixed(1);
|
|
991
|
-
if (!thinkingShown) {
|
|
992
|
-
if (thinkingBuffer.trim()) {
|
|
993
|
-
for (const w of wrapText(cleanThinkingLine(thinkingBuffer), boxWidth - 6)) {
|
|
994
|
-
printSolidBannerRow(` ${C.dim}${C.slate}${w}${C.reset}`, boxWidth, bg);
|
|
1339
|
+
if (globalTuiPrompt) {
|
|
1340
|
+
globalTuiPrompt.agentState = '';
|
|
1341
|
+
globalTuiPrompt.render();
|
|
995
1342
|
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
: isNotFound
|
|
1031
|
-
? `${C.amber}[NOT FOUND]`
|
|
1032
|
-
: `${C.emerald}[OK]`;
|
|
1033
|
-
const countTag = count != null && !isError ? ` • ${C.cyberCyan}${count} results${C.reset}` : '';
|
|
1034
|
-
const timerTag = durationMs !== undefined ? ` • ${C.dim}${(durationMs / 1000).toFixed(2)}s${C.reset}` : '';
|
|
1035
|
-
|
|
1036
|
-
printSolidBannerRow(` ${C.slate}• ${C.bold}${C.cyberCyan}${toolName}${C.reset} ${statusLabel} ${(isError ? C.red + 'Failed' : isNotFound ? C.amber + 'No matching data' : C.white + 'OK') + C.reset}${countTag}${timerTag}`, boxWidth, C.bgPanel);
|
|
1037
|
-
|
|
1038
|
-
if (!isError && !isNotFound) {
|
|
1039
|
-
const preview = extractPreview(result, toolName);
|
|
1040
|
-
if (preview) {
|
|
1041
|
-
for (const pLine of wrapText('• ' + preview, boxWidth - 6)) {
|
|
1042
|
-
printSolidBannerRow(` ${C.dim}${C.slate}${pLine}${C.reset}`, boxWidth, bg);
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
let thinkingShown = false;
|
|
1346
|
+
let thinkingBuffer = '';
|
|
1347
|
+
let thinkingStartTime = 0;
|
|
1348
|
+
let accumulatedThinking = '';
|
|
1349
|
+
let recordedTools = [];
|
|
1350
|
+
let currentToolArgs = {};
|
|
1351
|
+
let completedIterations = 0;
|
|
1352
|
+
let finalAnswer = '';
|
|
1353
|
+
let finalMeta = {};
|
|
1354
|
+
|
|
1355
|
+
const persistQueryDetails = (status, error = null) => {
|
|
1356
|
+
lastQueryDetails = {
|
|
1357
|
+
number: queryNumber,
|
|
1358
|
+
prompt,
|
|
1359
|
+
answer: finalAnswer,
|
|
1360
|
+
thinking: accumulatedThinking,
|
|
1361
|
+
tools: recordedTools,
|
|
1362
|
+
status,
|
|
1363
|
+
error: error ? String(error.message || error) : null,
|
|
1364
|
+
provider: agent.client?.providerName || agent.client?.provider || 'unknown',
|
|
1365
|
+
model: agent.client?.model || 'unknown',
|
|
1366
|
+
language: finalMeta.language || null,
|
|
1367
|
+
iterations: completedIterations,
|
|
1368
|
+
maxIterations: agent.maxIterations,
|
|
1369
|
+
startedAt: queryStartedAt,
|
|
1370
|
+
completedAt: Date.now(),
|
|
1371
|
+
durationMs: finalMeta.durationMs ?? (Date.now() - queryStartedAt),
|
|
1372
|
+
timestamp: Date.now(),
|
|
1373
|
+
};
|
|
1374
|
+
if (queryNumber != null) {
|
|
1375
|
+
queryDetails.set(queryNumber, lastQueryDetails);
|
|
1376
|
+
if (queryDetails.size > 50) queryDetails.delete(queryDetails.keys().next().value);
|
|
1043
1377
|
}
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
startSpinner(`${C.slate}Thinking…${C.reset}`);
|
|
1381
|
+
|
|
1382
|
+
try {
|
|
1383
|
+
const returnedAnswer = await agent.ask(prompt, {
|
|
1384
|
+
signal,
|
|
1385
|
+
onStepStart: iteration => { completedIterations = Math.max(completedIterations, Number(iteration) || 0); },
|
|
1386
|
+
onReasoningStart: () => {
|
|
1387
|
+
startSpinner(`${C.slate}Thinking…${C.reset}`);
|
|
1388
|
+
thinkingStartTime = Date.now();
|
|
1389
|
+
thinkingBuffer = '';
|
|
1390
|
+
if (!thinkingShown) {
|
|
1391
|
+
console.log('');
|
|
1392
|
+
const icon = thinkingFrames[Math.floor(Date.now() / 200) % thinkingFrames.length];
|
|
1393
|
+
printSolidBannerRow(` ${C.lavender}${C.bold}${icon} Thinking${queryTag ? ` • ${queryTag}` : ''}${C.reset}`, boxWidth, bgHeader);
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
onReasoningDelta: (chunk) => {
|
|
1397
|
+
accumulatedThinking += chunk;
|
|
1398
|
+
thinkingBuffer += chunk;
|
|
1399
|
+
if (!thinkingShown) {
|
|
1400
|
+
const lines = thinkingBuffer.split('\n');
|
|
1401
|
+
thinkingBuffer = lines.pop();
|
|
1402
|
+
for (const line of lines) {
|
|
1403
|
+
if (line.trim()) {
|
|
1404
|
+
for (const w of wrapText(cleanThinkingLine(line), boxWidth - 6)) {
|
|
1405
|
+
printSolidBannerRow(` ${C.dim}${C.slate}${w}${C.reset}`, boxWidth, bg);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
onReasoningEnd: () => {
|
|
1412
|
+
const elapsed = ((Date.now() - thinkingStartTime) / 1000).toFixed(1);
|
|
1413
|
+
if (!thinkingShown) {
|
|
1414
|
+
if (thinkingBuffer.trim()) {
|
|
1415
|
+
for (const w of wrapText(cleanThinkingLine(thinkingBuffer), boxWidth - 6)) {
|
|
1416
|
+
printSolidBannerRow(` ${C.dim}${C.slate}${w}${C.reset}`, boxWidth, bg);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
printSolidBannerRow(` ${C.emerald}[DONE] Thought for ${elapsed}s${C.reset}`, boxWidth, bg);
|
|
1420
|
+
console.log('');
|
|
1421
|
+
} else {
|
|
1422
|
+
const detailHint = queryTag ? ` — F2 or /details ${queryTag}` : '';
|
|
1423
|
+
console.log(`\n ${C.dim}${C.slate}Thinking${queryTag ? ` • ${queryTag}` : ''} (${elapsed}s)${detailHint}${C.reset}`);
|
|
1424
|
+
}
|
|
1425
|
+
thinkingShown = true;
|
|
1426
|
+
},
|
|
1427
|
+
|
|
1428
|
+
onToolStart: (toolName, args) => {
|
|
1429
|
+
if (!toolSectionStarted) {
|
|
1430
|
+
console.log('');
|
|
1431
|
+
const detailHint = queryTag ? ` ${C.dim}Details: F2 or /details ${queryTag}` : '';
|
|
1432
|
+
printSolidBannerRow(` ${C.bold}${C.cyberCyan}Tools${queryTag ? ` • ${queryTag}` : ''}${C.reset}${detailHint}`, boxWidth, C.bgPanelHeader);
|
|
1433
|
+
toolSectionStarted = true;
|
|
1434
|
+
}
|
|
1435
|
+
currentToolArgs = args || {};
|
|
1436
|
+
const queryVal = args?.query || args?.url || Object.values(args || {})[0] || '';
|
|
1437
|
+
const displayQuery = typeof queryVal === 'string' ? queryVal : '';
|
|
1438
|
+
startSpinner(`${C.cyberCyan}• ${C.bold}${C.white}${toolName}${C.reset}${displayQuery ? ` ${C.slate}"${displayQuery}"${C.reset}` : ''}`);
|
|
1439
|
+
},
|
|
1440
|
+
|
|
1441
|
+
onToolResult: (toolName, result, durationMs) => {
|
|
1442
|
+
recordedTools.push({ name: toolName, args: currentToolArgs, result, duration: durationMs });
|
|
1443
|
+
|
|
1444
|
+
const count = result.count ?? result.returnedCount;
|
|
1445
|
+
const isError = result.status === 'error';
|
|
1446
|
+
const isNotFound = result.status === 'not_found' || count === 0;
|
|
1447
|
+
const statusLabel = isError
|
|
1448
|
+
? `${C.red}[ERROR]`
|
|
1449
|
+
: isNotFound
|
|
1450
|
+
? `${C.amber}[NOT FOUND]`
|
|
1451
|
+
: `${C.emerald}[OK]`;
|
|
1452
|
+
const countTag = count != null && !isError ? ` • ${C.cyberCyan}${count} results${C.reset}` : '';
|
|
1453
|
+
const timerTag = durationMs !== undefined ? ` • ${C.dim}${(durationMs / 1000).toFixed(2)}s${C.reset}` : '';
|
|
1454
|
+
|
|
1455
|
+
printSolidBannerRow(` ${C.slate}• ${C.bold}${C.cyberCyan}${toolName}${C.reset} ${statusLabel} ${(isError ? C.red + 'Failed' : isNotFound ? C.amber + 'No matching data' : C.white + 'OK') + C.reset}${countTag}${timerTag}`, boxWidth, C.bgPanel);
|
|
1456
|
+
|
|
1457
|
+
if (!isError && !isNotFound) {
|
|
1458
|
+
const preview = extractPreview(result, toolName);
|
|
1459
|
+
if (preview) {
|
|
1460
|
+
for (const pLine of wrapText('• ' + preview, boxWidth - 6)) {
|
|
1461
|
+
printSolidBannerRow(` ${C.dim}${C.slate}${pLine}${C.reset}`, boxWidth, bg);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
},
|
|
1466
|
+
|
|
1467
|
+
onTokenStart: () => {
|
|
1468
|
+
if (globalTuiPrompt) {
|
|
1469
|
+
globalTuiPrompt.agentState = 'Streaming…';
|
|
1470
|
+
globalTuiPrompt.agentStateTime = Date.now();
|
|
1471
|
+
globalTuiPrompt.render();
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
|
|
1475
|
+
onTokenDelta: (token) => {
|
|
1476
|
+
if (!hasPrintedResponseHeader) { hasPrintedResponseHeader = true; }
|
|
1477
|
+
responseLineBuffer += token;
|
|
1478
|
+
flushResponseBuffer(false);
|
|
1479
|
+
},
|
|
1480
|
+
|
|
1481
|
+
onFinalAnswer: (answer, meta = {}) => {
|
|
1482
|
+
stopSpinner();
|
|
1483
|
+
finalAnswer = String(answer || '');
|
|
1484
|
+
finalMeta = meta || {};
|
|
1485
|
+
if (!hasPrintedResponseHeader) {
|
|
1486
|
+
for (const line of (answer || '').split('\n')) { if (line.length > 0) printResponseLine(line); }
|
|
1487
|
+
flushPendingTableHeader();
|
|
1488
|
+
} else {
|
|
1489
|
+
flushResponseBuffer(true);
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
|
|
1493
|
+
onError: (err) => {
|
|
1494
|
+
stopSpinner();
|
|
1495
|
+
printRichCard({
|
|
1496
|
+
title: 'Execution Error',
|
|
1497
|
+
status: 'error',
|
|
1498
|
+
lines: [err.message],
|
|
1499
|
+
});
|
|
1500
|
+
},
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
if (!finalAnswer && returnedAnswer) finalAnswer = String(returnedAnswer);
|
|
1504
|
+
persistQueryDetails('complete');
|
|
1505
|
+
if (responsePanelStarted) console.log('');
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
stopSpinner();
|
|
1508
|
+
persistQueryDetails(isAbortError(err) ? 'aborted' : 'error', err);
|
|
1509
|
+
if (!isAbortError(err)) {
|
|
1510
|
+
printRichCard({
|
|
1511
|
+
title: 'Query Failed',
|
|
1512
|
+
status: 'error',
|
|
1513
|
+
lines: [err.message],
|
|
1514
|
+
});
|
|
1515
|
+
} else {
|
|
1516
|
+
printRichCard({
|
|
1517
|
+
title: 'Query Cancelled',
|
|
1518
|
+
status: 'warning',
|
|
1519
|
+
lines: ['Query processing was interrupted.'],
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
} finally {
|
|
1523
|
+
stopSpinner();
|
|
1044
1524
|
}
|
|
1045
1525
|
}
|
|
1046
|
-
},
|
|
1047
|
-
|
|
1048
|
-
onTokenStart: () => { stopSpinner(); },
|
|
1049
|
-
|
|
1050
|
-
onTokenDelta: (token) => {
|
|
1051
|
-
if (!hasPrintedResponseHeader) { stopSpinner(); hasPrintedResponseHeader = true; }
|
|
1052
|
-
responseLineBuffer += token;
|
|
1053
|
-
flushResponseBuffer(false);
|
|
1054
|
-
},
|
|
1055
|
-
|
|
1056
|
-
onFinalAnswer: (answer) => {
|
|
1057
|
-
stopSpinner();
|
|
1058
|
-
if (!hasPrintedResponseHeader) {
|
|
1059
|
-
for (const line of (answer || '').split('\n')) { if (line.length > 0) printResponseLine(line); }
|
|
1060
|
-
flushPendingTableHeader();
|
|
1061
|
-
} else {
|
|
1062
|
-
flushResponseBuffer(true);
|
|
1063
|
-
}
|
|
1064
|
-
},
|
|
1065
|
-
|
|
1066
|
-
onError: (err) => {
|
|
1067
|
-
stopSpinner();
|
|
1068
|
-
console.log(`\n ${C.red}[ERROR] ${err.message}${C.reset}\n`);
|
|
1069
|
-
},
|
|
1070
|
-
});
|
|
1071
|
-
|
|
1072
|
-
lastQueryDetails = { number: queryNumber, prompt, thinking: accumulatedThinking, tools: recordedTools, timestamp: Date.now() };
|
|
1073
|
-
if (queryNumber != null) {
|
|
1074
|
-
queryDetails.set(queryNumber, lastQueryDetails);
|
|
1075
|
-
if (queryDetails.size > 50) queryDetails.delete(queryDetails.keys().next().value);
|
|
1076
|
-
}
|
|
1077
|
-
if (responsePanelStarted) console.log('');
|
|
1078
|
-
} catch (err) {
|
|
1079
|
-
stopSpinner();
|
|
1080
|
-
if (!isAbortError(err)) {
|
|
1081
|
-
console.log(`\n ${C.red}[ERROR] ${err.message}${C.reset}\n`);
|
|
1082
|
-
} else {
|
|
1083
|
-
console.log(`\n ${C.amber}[INTERRUPTED] Query cancelled.${C.reset}\n`);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
1526
|
|
|
1088
1527
|
async function main() {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1528
|
+
const opts = parseCliArgs();
|
|
1529
|
+
|
|
1530
|
+
if (opts.version) { console.log(PACKAGE_VERSION); process.exit(0); }
|
|
1531
|
+
if (opts.help) { printWelcomeHero(); printHelp(); process.exit(0); }
|
|
1532
|
+
if (opts.showTools) {
|
|
1533
|
+
printWelcomeHero();
|
|
1534
|
+
console.log(`\n ${C.bold}${C.sakura}Tools (${toolsSchema.length})${C.reset}\n`);
|
|
1535
|
+
for (const [i, t] of toolsSchema.entries()) {
|
|
1536
|
+
console.log(` ${C.gold}${String(i + 1).padStart(2)}${C.reset} ${C.cyberCyan}${t.function.name}${C.reset} — ${C.dim}${t.function.description.slice(0, 60)}${C.reset}`);
|
|
1537
|
+
}
|
|
1538
|
+
console.log('');
|
|
1539
|
+
process.exit(0);
|
|
1540
|
+
}
|
|
1092
1541
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1542
|
+
if (opts.showStats) { printWelcomeHero(); const r = await executeTool('get_system_stats', {}); console.log(JSON.stringify(r, null, 2)); process.exit(0); }
|
|
1543
|
+
|
|
1544
|
+
if (opts.query) {
|
|
1545
|
+
printWelcomeHero();
|
|
1546
|
+
const client = new LLMClient({
|
|
1547
|
+
provider: opts.provider || config.activeProvider,
|
|
1548
|
+
baseUrl: opts.url,
|
|
1549
|
+
apiKey: opts.apiKey,
|
|
1550
|
+
model: opts.model
|
|
1551
|
+
});
|
|
1552
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1553
|
+
const modelResolution = await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
1554
|
+
if (!modelResolution.connected || !modelResolution.selectedModel) {
|
|
1555
|
+
throw new Error(modelResolution.error || `No available chat model found for ${client.providerName}`);
|
|
1556
|
+
}
|
|
1557
|
+
if (modelResolution.usedFallback) {
|
|
1558
|
+
console.log(` ${C.amber}Model ${modelResolution.requestedModel} is unavailable; using ${modelResolution.selectedModel}.${C.reset}\n`);
|
|
1559
|
+
}
|
|
1560
|
+
console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${C.white}${opts.query}${C.reset}\n`);
|
|
1561
|
+
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
1562
|
+
await executeQueryWithUI(agent, opts.query, agent.session.thinkingMode === 'show', null, 1);
|
|
1563
|
+
process.exit(0);
|
|
1564
|
+
}
|
|
1103
1565
|
|
|
1104
|
-
if (opts.showStats) { printWelcomeHero(); const r = await executeTool('get_system_stats', {}); console.log(JSON.stringify(r, null, 2)); process.exit(0); }
|
|
1105
1566
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
const client = new LLMClient({
|
|
1109
|
-
provider: opts.provider || config.activeProvider,
|
|
1110
|
-
baseUrl: opts.url,
|
|
1111
|
-
apiKey: opts.apiKey,
|
|
1112
|
-
model: opts.model
|
|
1113
|
-
});
|
|
1114
|
-
console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${C.white}${opts.query}${C.reset}\n`);
|
|
1115
|
-
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
1116
|
-
await executeQueryWithUI(agent, opts.query, agent.session.thinkingMode === 'show', null, 1);
|
|
1117
|
-
process.exit(0);
|
|
1118
|
-
}
|
|
1567
|
+
try { process.stdout.write('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l'); } catch { }
|
|
1568
|
+
enterAlternateScreen();
|
|
1119
1569
|
|
|
1570
|
+
const client = new LLMClient({
|
|
1571
|
+
provider: opts.provider || config.activeProvider,
|
|
1572
|
+
baseUrl: opts.url,
|
|
1573
|
+
apiKey: opts.apiKey,
|
|
1574
|
+
model: opts.model
|
|
1575
|
+
});
|
|
1576
|
+
const baseUrl = client.baseUrl;
|
|
1577
|
+
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
1578
|
+
|
|
1579
|
+
function cancelActiveQuery() {
|
|
1580
|
+
let cancelled = false;
|
|
1581
|
+
if (activeAbortController) {
|
|
1582
|
+
activeAbortController.abort();
|
|
1583
|
+
activeAbortController = null;
|
|
1584
|
+
cancelled = true;
|
|
1585
|
+
}
|
|
1586
|
+
if (agent && typeof agent.cancelResearch === 'function') {
|
|
1587
|
+
agent.cancelResearch();
|
|
1588
|
+
cancelled = true;
|
|
1589
|
+
}
|
|
1590
|
+
if (queryQueue.length > 0) {
|
|
1591
|
+
queryQueue.length = 0;
|
|
1592
|
+
cancelled = true;
|
|
1593
|
+
}
|
|
1594
|
+
if (cancelled) {
|
|
1595
|
+
if (globalTuiPrompt) {
|
|
1596
|
+
globalTuiPrompt.agentState = '';
|
|
1597
|
+
globalTuiPrompt.render();
|
|
1598
|
+
}
|
|
1599
|
+
return true;
|
|
1600
|
+
}
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1120
1603
|
|
|
1121
|
-
|
|
1604
|
+
const tuiPrompt = new InteractiveTuiPrompt({
|
|
1605
|
+
prompt: 'Kiku ❯ ',
|
|
1606
|
+
agent,
|
|
1607
|
+
modelName: opts.model || client.model || 'gemma-4-e4b',
|
|
1608
|
+
provider: client.providerName,
|
|
1609
|
+
onCancel: cancelActiveQuery,
|
|
1610
|
+
onSigInt: async () => {
|
|
1611
|
+
if (activeAbortController || isProcessing) {
|
|
1612
|
+
cancelActiveQuery();
|
|
1613
|
+
} else {
|
|
1614
|
+
try { await agent.endSession(); } catch { }
|
|
1615
|
+
if (globalTuiPrompt) {
|
|
1616
|
+
globalTuiPrompt.dispose();
|
|
1617
|
+
globalTuiPrompt = null;
|
|
1618
|
+
}
|
|
1619
|
+
exitAlternateScreen();
|
|
1620
|
+
console.log(`\n ${C.sakura}Goodbye. (鞠躬 ~ 谢谢!)${C.reset}\n`);
|
|
1621
|
+
process.exit(0);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1122
1625
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1626
|
+
globalTuiPrompt = tuiPrompt;
|
|
1627
|
+
setupOutputInterception(tuiPrompt, () => isProcessing);
|
|
1628
|
+
|
|
1629
|
+
let health = await client.checkConnection();
|
|
1630
|
+
let catalogOnlyMode = false;
|
|
1631
|
+
|
|
1632
|
+
if (!health.connected) {
|
|
1633
|
+
printRichCard({
|
|
1634
|
+
title: `${client.providerName} Offline`,
|
|
1635
|
+
status: 'offline',
|
|
1636
|
+
lines: [
|
|
1637
|
+
`• Endpoint: ${baseUrl || 'local'} (${health.error || 'unreachable'})`,
|
|
1638
|
+
...(client.provider === 'lmstudio' ? ['• Action: Attempting auto-start of local LM Studio...'] : []),
|
|
1639
|
+
],
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
if (client.provider === 'lmstudio') {
|
|
1643
|
+
const autoHealth = await attemptAutoStartLMStudio(baseUrl, opts.model || client.model);
|
|
1644
|
+
if (autoHealth) {
|
|
1645
|
+
health = autoHealth;
|
|
1646
|
+
printRichCard({
|
|
1647
|
+
title: 'LM Studio Started',
|
|
1648
|
+
status: 'ok',
|
|
1649
|
+
lines: [
|
|
1650
|
+
'Local LM Studio process started and responding.',
|
|
1651
|
+
],
|
|
1652
|
+
});
|
|
1653
|
+
} else {
|
|
1654
|
+
catalogOnlyMode = true;
|
|
1655
|
+
printRichCard({
|
|
1656
|
+
title: 'Catalog-Only Mode',
|
|
1657
|
+
status: 'catalog',
|
|
1658
|
+
lines: [
|
|
1659
|
+
'Direct database & MCP tools remain available without AI synthesis.',
|
|
1660
|
+
'Start LM Studio and run /model reconnect to enable conversational chat.',
|
|
1661
|
+
],
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
} else {
|
|
1665
|
+
catalogOnlyMode = true;
|
|
1666
|
+
printRichCard({
|
|
1667
|
+
title: 'Catalog-Only Mode',
|
|
1668
|
+
status: 'catalog',
|
|
1669
|
+
lines: [
|
|
1670
|
+
'Direct database & MCP tools remain available without AI synthesis.',
|
|
1671
|
+
'Verify your provider endpoint/key and use /model reconnect.',
|
|
1672
|
+
],
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1155
1676
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1677
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1678
|
+
const modelResolution = catalogOnlyMode
|
|
1679
|
+
? { connected: false, state: 'catalog_only' }
|
|
1680
|
+
: await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
1681
|
+
if (!catalogOnlyMode && (!modelResolution.connected || !modelResolution.selectedModel)) {
|
|
1682
|
+
printRichCard({
|
|
1683
|
+
title: 'Model Not Loaded',
|
|
1684
|
+
status: 'warning',
|
|
1685
|
+
lines: [
|
|
1686
|
+
modelResolution.error || 'No chat-capable model loaded',
|
|
1687
|
+
'Load a model in LM Studio, then type /model reconnect.',
|
|
1688
|
+
],
|
|
1689
|
+
});
|
|
1690
|
+
catalogOnlyMode = true;
|
|
1691
|
+
} else if (!catalogOnlyMode && modelResolution.selectedModel) {
|
|
1692
|
+
tuiPrompt.modelName = modelResolution.selectedModel;
|
|
1693
|
+
printRichCard({
|
|
1694
|
+
title: 'Connected & Ready',
|
|
1695
|
+
status: 'connected',
|
|
1696
|
+
lines: [
|
|
1697
|
+
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${modelResolution.selectedModel}`,
|
|
1698
|
+
`• Status: Chat & Tool DAG Active • Tools: ${toolsSchema.length} Loaded`,
|
|
1699
|
+
],
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
if (modelResolution.usedFallback) {
|
|
1703
|
+
printRichCard({
|
|
1704
|
+
title: 'Model Fallback',
|
|
1705
|
+
status: 'warning',
|
|
1706
|
+
lines: [
|
|
1707
|
+
`Requested model ${opts.model} was unavailable.`,
|
|
1708
|
+
`Fallback model ${modelResolution.selectedModel} selected.`,
|
|
1709
|
+
],
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1158
1712
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1713
|
+
if (opts.mcp) {
|
|
1714
|
+
try {
|
|
1715
|
+
if (opts.mcp.endsWith('.json')) {
|
|
1716
|
+
await agent.mcpManager.loadConfigFile(opts.mcp);
|
|
1717
|
+
printRichCard({
|
|
1718
|
+
title: 'MCP Configuration',
|
|
1719
|
+
status: 'mcp',
|
|
1720
|
+
lines: [`Loaded MCP configuration from ${opts.mcp}`],
|
|
1721
|
+
});
|
|
1722
|
+
} else {
|
|
1723
|
+
const res = await agent.connectMcp('remote', { url: opts.mcp });
|
|
1724
|
+
printRichCard({
|
|
1725
|
+
title: 'MCP Connected',
|
|
1726
|
+
status: 'mcp',
|
|
1727
|
+
lines: [
|
|
1728
|
+
`Server: ${opts.mcp}`,
|
|
1729
|
+
`Tools registered: ${res.toolsCount}`,
|
|
1730
|
+
],
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
} catch (err) {
|
|
1734
|
+
printRichCard({
|
|
1735
|
+
title: 'MCP Error',
|
|
1736
|
+
status: 'error',
|
|
1737
|
+
lines: [err.message],
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1161
1741
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1742
|
+
await agent._ready;
|
|
1743
|
+
|
|
1744
|
+
const mcpStatus = agent.mcpManager ? agent.mcpManager.getStatus() : null;
|
|
1745
|
+
if (mcpStatus && mcpStatus.activeServersCount > 0 && !opts.mcp) {
|
|
1746
|
+
const serverSummaries = mcpStatus.servers
|
|
1747
|
+
.filter(s => s.status === 'connected')
|
|
1748
|
+
.map(s => `• ${s.name} (${s.type}) — ${s.toolsCount} tools`);
|
|
1749
|
+
printRichCard({
|
|
1750
|
+
title: 'Persisted MCP Connected',
|
|
1751
|
+
status: 'mcp',
|
|
1752
|
+
lines: [
|
|
1753
|
+
`Reconnected ${mcpStatus.activeServersCount} saved MCP server(s):`,
|
|
1754
|
+
...serverSummaries,
|
|
1755
|
+
],
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1165
1758
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
health = autoHealth;
|
|
1171
|
-
console.log(` ${C.emerald}[OK] LM Studio started.${C.reset}\n`);
|
|
1172
|
-
} else {
|
|
1173
|
-
catalogOnlyMode = true;
|
|
1174
|
-
console.log(` ${C.amber}[CATALOG-ONLY] Kiku will remain available without AI synthesis.${C.reset}\n`);
|
|
1175
|
-
}
|
|
1176
|
-
} else {
|
|
1177
|
-
catalogOnlyMode = true;
|
|
1178
|
-
console.log(` ${C.amber}[CATALOG-ONLY] Kiku will remain available without AI synthesis.${C.reset}\n`);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1759
|
+
if (agent.restoredMessageCount > 0) {
|
|
1760
|
+
const s = agent.session;
|
|
1761
|
+
console.log(`\n ${C.sakura}${C.bold}[SESSION RESTORED]${C.reset} ${C.dim}${s.id} — ${s.messages.length} messages${C.reset}\n`);
|
|
1762
|
+
}
|
|
1181
1763
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
console.log(` ${C.amber}[CATALOG-ONLY] Load or select a chat model, then use /model reconnect.${C.reset}\n`);
|
|
1190
|
-
}
|
|
1191
|
-
if (modelResolution.selectedModel) {
|
|
1192
|
-
tuiPrompt.modelName = modelResolution.selectedModel;
|
|
1193
|
-
}
|
|
1194
|
-
if (modelResolution.usedFallback) {
|
|
1195
|
-
console.log(` ${C.amber}[MODEL FALLBACK] Requested ${opts.model}, using ${modelResolution.selectedModel}.${C.reset}\n`);
|
|
1196
|
-
}
|
|
1764
|
+
agent.stateMachine.on('enter:' + AgentStates.FETCHING_TOOL, ({ toolName }) => {
|
|
1765
|
+
if (globalTuiPrompt) {
|
|
1766
|
+
globalTuiPrompt.agentState = `Tool: ${toolName}`;
|
|
1767
|
+
globalTuiPrompt.agentStateTime = Date.now();
|
|
1768
|
+
globalTuiPrompt.render();
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1197
1771
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
console.log(` ${C.emerald}[OK] MCP Connected: ${opts.mcp} (${res.toolsCount} Tools)${C.reset}\n`);
|
|
1206
|
-
}
|
|
1207
|
-
} catch (err) {
|
|
1208
|
-
console.log(` ${C.red}[MCP Error] ${err.message}${C.reset}\n`);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1772
|
+
agent.stateMachine.on('enter:' + AgentStates.STREAMING_THOUGHTS, () => {
|
|
1773
|
+
if (globalTuiPrompt) {
|
|
1774
|
+
globalTuiPrompt.agentState = 'Thinking…';
|
|
1775
|
+
globalTuiPrompt.agentStateTime = Date.now();
|
|
1776
|
+
globalTuiPrompt.render();
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1211
1779
|
|
|
1212
|
-
|
|
1780
|
+
agent.stateMachine.on('enter:' + AgentStates.STREAMING_ANSWER, () => {
|
|
1781
|
+
if (globalTuiPrompt) {
|
|
1782
|
+
globalTuiPrompt.agentState = 'Streaming…';
|
|
1783
|
+
globalTuiPrompt.agentStateTime = Date.now();
|
|
1784
|
+
globalTuiPrompt.render();
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1213
1787
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1788
|
+
agent.stateMachine.on('enter:' + AgentStates.IDLE, () => {
|
|
1789
|
+
if (globalTuiPrompt) {
|
|
1790
|
+
globalTuiPrompt.agentState = '';
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1218
1793
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1794
|
+
const cmdCtx = {
|
|
1795
|
+
tuiPrompt,
|
|
1796
|
+
agent,
|
|
1797
|
+
client,
|
|
1798
|
+
baseUrl,
|
|
1799
|
+
modelManager,
|
|
1800
|
+
catalogOnlyMode,
|
|
1801
|
+
setCatalogOnlyMode: value => { catalogOnlyMode = Boolean(value); },
|
|
1802
|
+
get lastQuery() { return lastQuery; },
|
|
1803
|
+
set lastQuery(value) { lastQuery = value; },
|
|
1804
|
+
printHelp,
|
|
1805
|
+
printQueryDetails,
|
|
1806
|
+
copyQueryDetails,
|
|
1807
|
+
printWelcome: printWelcomeHero,
|
|
1808
|
+
printAd,
|
|
1809
|
+
cancelActiveQuery,
|
|
1810
|
+
enqueueQuery: async (prompt, isRetry) => {
|
|
1811
|
+
queryQueue.push({ id: 'q_' + Date.now(), number: nextQueryNumber++, prompt, addedAt: Date.now() });
|
|
1812
|
+
triggerQueueProcessing();
|
|
1813
|
+
},
|
|
1814
|
+
clearQueryQueue: () => { queryQueue.length = 0; },
|
|
1815
|
+
getQueryQueueLength: () => queryQueue.length,
|
|
1816
|
+
get isProcessing() { return isProcessing; },
|
|
1817
|
+
setIsProcessing: (val, label = '') => {
|
|
1818
|
+
isProcessing = Boolean(val);
|
|
1819
|
+
if (globalTuiPrompt) {
|
|
1820
|
+
globalTuiPrompt.agentState = label || '';
|
|
1821
|
+
if (label) globalTuiPrompt.agentStateTime = Date.now();
|
|
1822
|
+
globalTuiPrompt.render();
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1825
|
+
setAgentState: (label, time = Date.now()) => {
|
|
1826
|
+
if (globalTuiPrompt) {
|
|
1827
|
+
globalTuiPrompt.agentState = label || '';
|
|
1828
|
+
globalTuiPrompt.agentStateTime = time;
|
|
1829
|
+
globalTuiPrompt.render();
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
const triggerQueueProcessing = () => {
|
|
1835
|
+
if (isProcessing || queryQueue.length === 0) return;
|
|
1836
|
+
isProcessing = true;
|
|
1837
|
+
|
|
1838
|
+
(async () => {
|
|
1839
|
+
try {
|
|
1840
|
+
while (queryQueue.length > 0) {
|
|
1841
|
+
const nextItem = queryQueue.shift();
|
|
1842
|
+
activeQueryNumber = nextItem.number;
|
|
1843
|
+
cmdCtx.lastQuery = nextItem.prompt;
|
|
1844
|
+
activeAbortController = new AbortController();
|
|
1845
|
+
|
|
1846
|
+
try {
|
|
1847
|
+
if (catalogOnlyMode) {
|
|
1848
|
+
const targetTool = nextItem.prompt.startsWith('@')
|
|
1849
|
+
? nextItem.prompt.slice(1).split(/\s+/)[0]
|
|
1850
|
+
: 'search_works';
|
|
1851
|
+
const rawArg = nextItem.prompt.startsWith('@')
|
|
1852
|
+
? nextItem.prompt.slice(targetTool.length + 1).trim()
|
|
1853
|
+
: nextItem.prompt;
|
|
1854
|
+
|
|
1855
|
+
let queryArgs = {};
|
|
1856
|
+
try { queryArgs = JSON.parse(rawArg); } catch { queryArgs = { query: rawArg }; }
|
|
1857
|
+
|
|
1858
|
+
let result;
|
|
1859
|
+
if (agent.mcpManager?.isMcpTool(targetTool)) {
|
|
1860
|
+
result = await agent.mcpManager.callTool(targetTool, queryArgs);
|
|
1861
|
+
} else {
|
|
1862
|
+
result = await executeTool(targetTool, queryArgs);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
const serialized = JSON.stringify(result, null, 2);
|
|
1866
|
+
printSolidPanel({
|
|
1867
|
+
title: `Direct Tool Result • ${targetTool} • Q${nextItem.number}`,
|
|
1868
|
+
lines: serialized.split('\n'),
|
|
1869
|
+
});
|
|
1870
|
+
} else {
|
|
1871
|
+
await executeQueryWithUI(agent, nextItem.prompt, agent.session.thinkingMode === 'show', activeAbortController.signal, nextItem.number);
|
|
1872
|
+
}
|
|
1873
|
+
} catch (err) {
|
|
1874
|
+
if (!isAbortError(err)) {
|
|
1875
|
+
printRichCard({
|
|
1876
|
+
title: 'Execution Error',
|
|
1877
|
+
status: 'error',
|
|
1878
|
+
lines: [err.message],
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
} finally {
|
|
1882
|
+
activeAbortController = null;
|
|
1883
|
+
queryCount++;
|
|
1884
|
+
printAd();
|
|
1885
|
+
activeQueryNumber = null;
|
|
1886
|
+
if (globalTuiPrompt) {
|
|
1887
|
+
globalTuiPrompt.agentState = '';
|
|
1888
|
+
globalTuiPrompt.render();
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
} finally {
|
|
1893
|
+
isProcessing = false;
|
|
1894
|
+
if (globalTuiPrompt) {
|
|
1895
|
+
globalTuiPrompt.agentState = '';
|
|
1896
|
+
globalTuiPrompt.render();
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
})();
|
|
1900
|
+
};
|
|
1901
|
+
|
|
1902
|
+
while (true) {
|
|
1903
|
+
let rawInput;
|
|
1904
|
+
|
|
1905
|
+
if (deferredCommands.length > 0 && !isProcessing) {
|
|
1906
|
+
rawInput = deferredCommands.shift();
|
|
1907
|
+
printRichCard({
|
|
1908
|
+
title: 'Deferred Command Executed',
|
|
1909
|
+
status: 'info',
|
|
1910
|
+
lines: [rawInput],
|
|
1911
|
+
});
|
|
1912
|
+
} else {
|
|
1913
|
+
rawInput = await tuiPrompt.readLine();
|
|
1914
|
+
}
|
|
1225
1915
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
globalTuiPrompt.agentStateTime = Date.now();
|
|
1230
|
-
}
|
|
1231
|
-
});
|
|
1916
|
+
if (rawInput === null) break;
|
|
1917
|
+
const trimmed = rawInput.trim();
|
|
1918
|
+
if (!trimmed) continue;
|
|
1232
1919
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1920
|
+
if (trimmed.startsWith('/')) {
|
|
1921
|
+
const lower = trimmed.toLowerCase();
|
|
1922
|
+
if (lower === '/cancel' || lower === '/abort' || (lower === '/stop' && isProcessing)) {
|
|
1923
|
+
cancelActiveQuery();
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1239
1926
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1927
|
+
if (isProcessing) {
|
|
1928
|
+
deferredCommands.push(trimmed);
|
|
1929
|
+
printRichCard({
|
|
1930
|
+
title: 'Command Queued',
|
|
1931
|
+
status: 'info',
|
|
1932
|
+
lines: [`• "${trimmed}" queued to run when active processing finishes.`],
|
|
1933
|
+
});
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1245
1936
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1937
|
+
const parts = trimmed.split(/\s+/);
|
|
1938
|
+
const sub = parts[1] || '';
|
|
1939
|
+
const extra = parts.slice(2).join(' ');
|
|
1940
|
+
|
|
1941
|
+
let result;
|
|
1942
|
+
try {
|
|
1943
|
+
result = await routeCommand(trimmed, {
|
|
1944
|
+
...cmdCtx,
|
|
1945
|
+
sub,
|
|
1946
|
+
extra,
|
|
1947
|
+
parts,
|
|
1948
|
+
});
|
|
1949
|
+
} catch (cmdErr) {
|
|
1950
|
+
printRichCard({
|
|
1951
|
+
title: 'Command Error',
|
|
1952
|
+
status: 'error',
|
|
1953
|
+
lines: [cmdErr.message || String(cmdErr)],
|
|
1954
|
+
});
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1248
1957
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1958
|
+
if (result === 'EXIT') {
|
|
1959
|
+
cancelActiveQuery();
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
1962
|
+
if (result === 'RETRY' && cmdCtx.lastQuery) {
|
|
1963
|
+
const retryNumber = nextQueryNumber++;
|
|
1964
|
+
printSolidPanel({
|
|
1965
|
+
title: `${C.roseGlow}${C.bold}Question ${retryNumber} • Retry${C.reset}`,
|
|
1966
|
+
lines: wrapText(cmdCtx.lastQuery, getFullWidth() - 8)
|
|
1967
|
+
.map(wLine => `${C.cyberCyan}❯${C.reset} ${C.white}${wLine}${C.reset}`),
|
|
1968
|
+
bg: C.bgPanel
|
|
1969
|
+
});
|
|
1970
|
+
queryQueue.push({ id: 'q_' + Date.now(), number: retryNumber, prompt: cmdCtx.lastQuery, addedAt: Date.now() });
|
|
1971
|
+
triggerQueueProcessing();
|
|
1972
|
+
}
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1254
1975
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
targetTool = atMatch[1];
|
|
1263
|
-
const rawArg = (atMatch[2] || '').trim();
|
|
1264
|
-
if (rawArg) {
|
|
1265
|
-
try { queryArgs = JSON.parse(rawArg); } catch { queryArgs = { query: rawArg }; }
|
|
1266
|
-
} else {
|
|
1267
|
-
queryArgs = {};
|
|
1976
|
+
const { sanitized, warnings } = sanitizeQuery(trimmed);
|
|
1977
|
+
if (warnings.length > 0) {
|
|
1978
|
+
printRichCard({
|
|
1979
|
+
title: 'Query Warning',
|
|
1980
|
+
status: 'warning',
|
|
1981
|
+
lines: warnings,
|
|
1982
|
+
});
|
|
1268
1983
|
}
|
|
1269
|
-
}
|
|
1270
1984
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1985
|
+
const queryNumber = nextQueryNumber++;
|
|
1986
|
+
const queuePosition = isProcessing ? queryQueue.length + 1 : 0;
|
|
1987
|
+
const queuedLabel = queuePosition > 0 ? ` • Queued ${queuePosition}` : '';
|
|
1988
|
+
printSolidPanel({
|
|
1989
|
+
title: `${C.roseGlow}${C.bold}Question ${queryNumber} • ${randomFanName()}${queuedLabel}${C.reset}`,
|
|
1990
|
+
lines: wrapText(trimmed, getFullWidth() - 8).map(wLine => `${C.cyberCyan}❯${C.reset} ${C.white}${wLine}${C.reset}`),
|
|
1991
|
+
bg: C.bgPanel
|
|
1992
|
+
});
|
|
1993
|
+
|
|
1994
|
+
lastQuery = trimmed;
|
|
1995
|
+
queryQueue.push({ id: 'q_' + Date.now(), number: queryNumber, prompt: trimmed, addedAt: Date.now() });
|
|
1996
|
+
triggerQueueProcessing();
|
|
1276
1997
|
}
|
|
1277
1998
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
});
|
|
1286
|
-
} else {
|
|
1287
|
-
await executeQueryWithUI(agent, nextItem.prompt, agent.session.thinkingMode === 'show', activeAbortController.signal, nextItem.number);
|
|
1999
|
+
if (globalTuiPrompt) {
|
|
2000
|
+
globalTuiPrompt.dispose();
|
|
2001
|
+
globalTuiPrompt = null;
|
|
2002
|
+
}
|
|
2003
|
+
exitAlternateScreen();
|
|
2004
|
+
console.log(`\n ${C.sakura}Goodbye. (鞠躬 ~ 谢谢!)${C.reset}\n`);
|
|
2005
|
+
process.exit(0);
|
|
1288
2006
|
}
|
|
1289
|
-
} finally {
|
|
1290
|
-
activeAbortController = null;
|
|
1291
|
-
queryCount++;
|
|
1292
|
-
printAd();
|
|
1293
|
-
activeQueryNumber = null;
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
isProcessing = false;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
while (true) {
|
|
1300
|
-
let rawInput;
|
|
1301
|
-
|
|
1302
|
-
if (deferredCommands.length > 0 && !isProcessing) {
|
|
1303
|
-
rawInput = deferredCommands.shift();
|
|
1304
|
-
console.log(`\n ${C.cyberCyan}[DEFERRED] ${rawInput}${C.reset}\n`);
|
|
1305
|
-
} else {
|
|
1306
|
-
rawInput = await tuiPrompt.readLine();
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
if (rawInput === null) break;
|
|
1310
|
-
const trimmed = rawInput.trim();
|
|
1311
|
-
if (!trimmed) continue;
|
|
1312
|
-
|
|
1313
|
-
const queryNumber = !trimmed.startsWith('/') ? nextQueryNumber++ : null;
|
|
1314
2007
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const queuedLabel = queuePosition > 0 ? ` • Queued ${queuePosition}` : '';
|
|
1319
|
-
printSolidPanel({
|
|
1320
|
-
title: `${C.roseGlow}${C.bold}Question ${queryNumber} • ${randomFanName()}${queuedLabel}${C.reset}`,
|
|
1321
|
-
lines: wrapped.map(wLine => `${C.cyberCyan}❯${C.reset} ${C.white}${wLine}${C.reset}`),
|
|
1322
|
-
bg: '\x1b[48;2;36;40;52m'
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
if (trimmed.startsWith('/')) {
|
|
1327
|
-
if (isProcessing) {
|
|
1328
|
-
deferredCommands.push(trimmed);
|
|
1329
|
-
console.log(`\n ${C.cyberCyan}[DEFERRED] ${trimmed}${C.reset}\n`);
|
|
1330
|
-
tuiPrompt.render();
|
|
1331
|
-
continue;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
const cmdCtx = {
|
|
1335
|
-
tuiPrompt,
|
|
1336
|
-
agent,
|
|
1337
|
-
client,
|
|
1338
|
-
baseUrl,
|
|
1339
|
-
modelManager,
|
|
1340
|
-
catalogOnlyMode,
|
|
1341
|
-
setCatalogOnlyMode: value => { catalogOnlyMode = Boolean(value); },
|
|
1342
|
-
sub: trimmed.split(/\s+/)[1] || '',
|
|
1343
|
-
extra: trimmed.split(/\s+/).slice(2).join(' '),
|
|
1344
|
-
parts: trimmed.split(/\s+/),
|
|
1345
|
-
lastQuery,
|
|
1346
|
-
printHelp,
|
|
1347
|
-
printQueryDetails,
|
|
1348
|
-
printWelcome: printWelcomeHero,
|
|
1349
|
-
printAd,
|
|
1350
|
-
enqueueQuery: async (prompt, isRetry) => {
|
|
1351
|
-
queryQueue.push({ id: 'q_' + Date.now(), number: nextQueryNumber++, prompt, addedAt: Date.now() });
|
|
1352
|
-
await processNextQueueItem();
|
|
1353
|
-
},
|
|
1354
|
-
clearQueryQueue: () => { queryQueue.length = 0; },
|
|
1355
|
-
getQueryQueueLength: () => queryQueue.length,
|
|
1356
|
-
};
|
|
1357
|
-
|
|
1358
|
-
const result = await routeCommand(trimmed, cmdCtx);
|
|
1359
|
-
|
|
1360
|
-
if (result === 'EXIT') break;
|
|
1361
|
-
if (result === 'RETRY' && cmdCtx.lastQuery) {
|
|
1362
|
-
const retryNumber = nextQueryNumber++;
|
|
1363
|
-
printSolidPanel({
|
|
1364
|
-
title: `${C.roseGlow}${C.bold}Question ${retryNumber} • Retry${C.reset}`,
|
|
1365
|
-
lines: wrapText(cmdCtx.lastQuery, getFullWidth() - 8)
|
|
1366
|
-
.map(wLine => `${C.cyberCyan}❯${C.reset} ${C.white}${wLine}${C.reset}`),
|
|
1367
|
-
bg: '\x1b[48;2;36;40;52m'
|
|
2008
|
+
process.on('exit', () => {
|
|
2009
|
+
try { globalTuiPrompt?.dispose(); } catch { }
|
|
2010
|
+
exitAlternateScreen();
|
|
1368
2011
|
});
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
lastQuery = trimmed;
|
|
1381
|
-
queryQueue.push({ id: 'q_' + Date.now(), number: queryNumber, prompt: trimmed, addedAt: Date.now() });
|
|
1382
|
-
await processNextQueueItem();
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
if (globalTuiPrompt) {
|
|
1386
|
-
globalTuiPrompt.dispose();
|
|
1387
|
-
globalTuiPrompt = null;
|
|
1388
|
-
}
|
|
1389
|
-
exitAlternateScreen();
|
|
1390
|
-
console.log(`\n ${C.sakura}Goodbye. (鞠躬 ~ 谢谢!)${C.reset}\n`);
|
|
1391
|
-
process.exit(0);
|
|
1392
|
-
}
|
|
2012
|
+
process.on('uncaughtException', (err) => {
|
|
2013
|
+
try { globalTuiPrompt?.dispose(); } catch { }
|
|
2014
|
+
exitAlternateScreen();
|
|
2015
|
+
console.error(`\n Uncaught: ${err.message}`);
|
|
2016
|
+
process.exit(1);
|
|
2017
|
+
});
|
|
2018
|
+
process.on('SIGINT', () => {
|
|
2019
|
+
try { globalTuiPrompt?.dispose(); } catch { }
|
|
2020
|
+
exitAlternateScreen();
|
|
2021
|
+
process.exit(130);
|
|
2022
|
+
});
|
|
1393
2023
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
try { globalTuiPrompt?.dispose(); } catch {}
|
|
1400
|
-
exitAlternateScreen();
|
|
1401
|
-
console.error(`\n Uncaught: ${err.message}`);
|
|
1402
|
-
process.exit(1);
|
|
1403
|
-
});
|
|
1404
|
-
process.on('SIGINT', () => {
|
|
1405
|
-
try { globalTuiPrompt?.dispose(); } catch {}
|
|
1406
|
-
exitAlternateScreen();
|
|
1407
|
-
process.exit(130);
|
|
1408
|
-
});
|
|
1409
|
-
|
|
1410
|
-
main().catch(err => {
|
|
1411
|
-
exitAlternateScreen();
|
|
1412
|
-
console.error(`\n ${C.red}Fatal: ${err.message}${C.reset}\n`);
|
|
1413
|
-
process.exit(1);
|
|
1414
|
-
});
|
|
2024
|
+
main().catch(err => {
|
|
2025
|
+
exitAlternateScreen();
|
|
2026
|
+
console.error(`\n ${C.red}Fatal: ${err.message}${C.reset}\n`);
|
|
2027
|
+
process.exit(1);
|
|
2028
|
+
});
|