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