openhorse 0.1.19 → 0.1.21
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/dist/cli.js +446 -33
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +99 -40
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/types.d.ts +14 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/framework/query.d.ts.map +1 -1
- package/dist/framework/query.js +25 -7
- package/dist/framework/query.js.map +1 -1
- package/dist/runtime/turn-controller.d.ts +37 -0
- package/dist/runtime/turn-controller.d.ts.map +1 -0
- package/dist/runtime/turn-controller.js +84 -0
- package/dist/runtime/turn-controller.js.map +1 -0
- package/dist/services/config.d.ts +4 -2
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +30 -0
- package/dist/services/config.js.map +1 -1
- package/dist/services/global-config.d.ts +13 -0
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js +4 -0
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/llm.d.ts +4 -1
- package/dist/services/llm.d.ts.map +1 -1
- package/dist/services/llm.js +15 -2
- package/dist/services/llm.js.map +1 -1
- package/dist/ui/command-panel.d.ts +17 -0
- package/dist/ui/command-panel.d.ts.map +1 -1
- package/dist/ui/command-panel.js +234 -93
- package/dist/ui/command-panel.js.map +1 -1
- package/dist/ui/file-completion.d.ts +3 -0
- package/dist/ui/file-completion.d.ts.map +1 -1
- package/dist/ui/file-completion.js +7 -2
- package/dist/ui/file-completion.js.map +1 -1
- package/dist/ui/multiline-input.d.ts +4 -0
- package/dist/ui/multiline-input.d.ts.map +1 -1
- package/dist/ui/multiline-input.js +7 -0
- package/dist/ui/multiline-input.js.map +1 -1
- package/dist/ui/user-input.d.ts +1 -0
- package/dist/ui/user-input.d.ts.map +1 -1
- package/dist/ui/user-input.js +8 -4
- package/dist/ui/user-input.js.map +1 -1
- package/dist/ui-v2/components/command-palette.d.ts +6 -0
- package/dist/ui-v2/components/command-palette.d.ts.map +1 -0
- package/dist/ui-v2/components/command-palette.js +57 -0
- package/dist/ui-v2/components/command-palette.js.map +1 -0
- package/dist/ui-v2/components/session-picker.d.ts +17 -0
- package/dist/ui-v2/components/session-picker.d.ts.map +1 -0
- package/dist/ui-v2/components/session-picker.js +23 -0
- package/dist/ui-v2/components/session-picker.js.map +1 -0
- package/dist/ui-v2/components/shell.d.ts +49 -0
- package/dist/ui-v2/components/shell.d.ts.map +1 -0
- package/dist/ui-v2/components/shell.js +190 -0
- package/dist/ui-v2/components/shell.js.map +1 -0
- package/dist/ui-v2/index.d.ts +10 -0
- package/dist/ui-v2/index.d.ts.map +1 -0
- package/dist/ui-v2/index.js +26 -0
- package/dist/ui-v2/index.js.map +1 -0
- package/dist/ui-v2/runtime/text.d.ts +8 -0
- package/dist/ui-v2/runtime/text.d.ts.map +1 -0
- package/dist/ui-v2/runtime/text.js +58 -0
- package/dist/ui-v2/runtime/text.js.map +1 -0
- package/dist/ui-v2/state/input-reducer.d.ts +39 -0
- package/dist/ui-v2/state/input-reducer.d.ts.map +1 -0
- package/dist/ui-v2/state/input-reducer.js +82 -0
- package/dist/ui-v2/state/input-reducer.js.map +1 -0
- package/dist/ui-v2/state/picker.d.ts +16 -0
- package/dist/ui-v2/state/picker.d.ts.map +1 -0
- package/dist/ui-v2/state/picker.js +52 -0
- package/dist/ui-v2/state/picker.js.map +1 -0
- package/dist/ui-v2/state/sessions.d.ts +14 -0
- package/dist/ui-v2/state/sessions.d.ts.map +1 -0
- package/dist/ui-v2/state/sessions.js +65 -0
- package/dist/ui-v2/state/sessions.js.map +1 -0
- package/dist/ui-v2/state/suggestions.d.ts +22 -0
- package/dist/ui-v2/state/suggestions.d.ts.map +1 -0
- package/dist/ui-v2/state/suggestions.js +55 -0
- package/dist/ui-v2/state/suggestions.js.map +1 -0
- package/dist/ui-v2/types.d.ts +33 -0
- package/dist/ui-v2/types.d.ts.map +1 -0
- package/dist/ui-v2/types.js +9 -0
- package/dist/ui-v2/types.js.map +1 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -34,6 +34,8 @@ const multiline_input_1 = require("./ui/multiline-input");
|
|
|
34
34
|
const file_completion_1 = require("./ui/file-completion");
|
|
35
35
|
const status_bar_1 = require("./ui/status-bar");
|
|
36
36
|
const user_input_1 = require("./ui/user-input");
|
|
37
|
+
const ui_v2_1 = require("./ui-v2");
|
|
38
|
+
const turn_controller_1 = require("./runtime/turn-controller");
|
|
37
39
|
// Get version from package.json
|
|
38
40
|
const VERSION = (() => {
|
|
39
41
|
try {
|
|
@@ -66,10 +68,12 @@ function showCliHelp() {
|
|
|
66
68
|
console.log(' openhorse Start interactive REPL');
|
|
67
69
|
console.log(' openhorse --help Show this help message');
|
|
68
70
|
console.log(' openhorse --version Show version');
|
|
71
|
+
console.log(' openhorse --ui v2 Enable UI v2 preview components');
|
|
69
72
|
console.log();
|
|
70
73
|
console.log(ACCENT('Options:'));
|
|
71
74
|
console.log(' -h, --help Show help');
|
|
72
75
|
console.log(' -v, --version Show version');
|
|
76
|
+
console.log(' --ui <mode> UI renderer: legacy | v2');
|
|
73
77
|
console.log();
|
|
74
78
|
console.log(ACCENT('Interactive Commands:'));
|
|
75
79
|
console.log(' /help Show available slash commands');
|
|
@@ -81,6 +85,24 @@ function showCliHelp() {
|
|
|
81
85
|
console.log(DIM('Type /help in REPL for full command list.'));
|
|
82
86
|
console.log();
|
|
83
87
|
}
|
|
88
|
+
function parseCliUIRenderer(args) {
|
|
89
|
+
for (let i = 0; i < args.length; i++) {
|
|
90
|
+
const arg = args[i];
|
|
91
|
+
const value = arg === '--ui'
|
|
92
|
+
? args[i + 1]
|
|
93
|
+
: arg.startsWith('--ui=')
|
|
94
|
+
? arg.slice('--ui='.length)
|
|
95
|
+
: undefined;
|
|
96
|
+
if (value === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
if (value === 'legacy' || value === 'v2')
|
|
99
|
+
return value;
|
|
100
|
+
console.error(ERROR(`Invalid --ui value: ${value}`));
|
|
101
|
+
console.error(DIM('Expected one of: legacy, v2'));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
84
106
|
// ============================================================================
|
|
85
107
|
// 全局状态
|
|
86
108
|
// ============================================================================
|
|
@@ -88,29 +110,108 @@ let llm = null;
|
|
|
88
110
|
let store;
|
|
89
111
|
let currentSession = null;
|
|
90
112
|
let runtime;
|
|
113
|
+
const turnController = new turn_controller_1.TurnController();
|
|
114
|
+
let isShuttingDown = false;
|
|
91
115
|
// 输入状态
|
|
92
116
|
let currentInput = '';
|
|
93
117
|
let inputHistory = [];
|
|
94
118
|
let historyIndex = -1;
|
|
95
119
|
let historyMode = 'none';
|
|
96
120
|
let searchQuery = '';
|
|
121
|
+
let resumePickerState = null;
|
|
122
|
+
let resumePickerLines = [];
|
|
123
|
+
let resumePickerReservedHeight = 0;
|
|
124
|
+
function ensureCurrentSession() {
|
|
125
|
+
if (!currentSession) {
|
|
126
|
+
currentSession = (0, session_storage_1.createSession)(process.cwd(), store.getSnapshot().currentModel || store.getSnapshot().config.model);
|
|
127
|
+
(0, global_config_1.incrementSessionCount)();
|
|
128
|
+
}
|
|
129
|
+
return currentSession;
|
|
130
|
+
}
|
|
131
|
+
function setCurrentSession(session) {
|
|
132
|
+
currentSession = session;
|
|
133
|
+
}
|
|
134
|
+
function getCurrentSession() {
|
|
135
|
+
return currentSession;
|
|
136
|
+
}
|
|
97
137
|
function echoSubmittedInput(input) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
138
|
+
(0, command_panel_1.clearRenderedInput)();
|
|
139
|
+
const rendered = isV2UI()
|
|
140
|
+
? (0, user_input_1.renderUserInputEchoFrame)(input)
|
|
141
|
+
: (0, user_input_1.renderUserInputEcho)(input);
|
|
142
|
+
(0, command_panel_1.writeLinePreservingInput)(rendered);
|
|
143
|
+
if (isV2UI()) {
|
|
144
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function isExitCommandInput(input) {
|
|
148
|
+
const parsed = (0, parser_1.parseInput)(input.trim());
|
|
149
|
+
if (!parsed.isCommand)
|
|
150
|
+
return false;
|
|
151
|
+
const cmd = (0, commands_1.findCommand)(parsed.name);
|
|
152
|
+
return cmd?.name === 'exit';
|
|
101
153
|
}
|
|
102
154
|
function submitInput(input) {
|
|
103
155
|
const submittedInput = input;
|
|
104
156
|
currentInput = '';
|
|
105
157
|
echoSubmittedInput(submittedInput);
|
|
106
|
-
handleInput(submittedInput).catch(err => {
|
|
107
|
-
console.log(ERROR(`Input error: ${err.message || String(err)}`));
|
|
108
|
-
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
109
|
-
});
|
|
110
158
|
(0, global_config_1.addToInputHistory)(submittedInput);
|
|
111
159
|
inputHistory = (0, global_config_1.getInputHistory)();
|
|
160
|
+
if (turnController.hasActiveTurn()) {
|
|
161
|
+
const text = submittedInput.trim();
|
|
162
|
+
const parsed = (0, parser_1.parseInput)(text);
|
|
163
|
+
if (isExitCommandInput(text)) {
|
|
164
|
+
void shutdownCli();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (parsed.isCommand) {
|
|
168
|
+
console.log(DIM('Command ignored while agent is running. Press Ctrl+C to interrupt first.'));
|
|
169
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (text) {
|
|
173
|
+
turnController.clearExitIntent();
|
|
174
|
+
turnController.requestRevision(submittedInput);
|
|
175
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
void runInputTurn(submittedInput);
|
|
180
|
+
}
|
|
181
|
+
async function runInputTurn(input) {
|
|
182
|
+
let nextInput = input;
|
|
183
|
+
while (nextInput) {
|
|
184
|
+
const turn = turnController.beginTurn(nextInput);
|
|
185
|
+
store.setProcessing(true);
|
|
186
|
+
try {
|
|
187
|
+
await handleInput(nextInput, turn.abortSignal, { redrawPrompt: false, updateStatus: false });
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
console.log(ERROR(`Input error: ${err.message || String(err)}`));
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
const revision = turnController.finishTurn(turn.id);
|
|
194
|
+
store.setProcessing(false);
|
|
195
|
+
if (revision && revision.trim()) {
|
|
196
|
+
(0, command_panel_1.writeLinePreservingInput)(DIM('Interrupted. Restarting with latest instruction...'));
|
|
197
|
+
nextInput = revision;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
nextInput = undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
updateStatusBar();
|
|
205
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
112
206
|
}
|
|
113
207
|
function parseKey(char, key) {
|
|
208
|
+
const sequence = key?.sequence || char || '';
|
|
209
|
+
if (sequence === '\x1b[13;2u') {
|
|
210
|
+
return { name: 'enter', ctrl: false, shift: true, meta: false, sequence };
|
|
211
|
+
}
|
|
212
|
+
if (sequence === '\x1b[13;3u') {
|
|
213
|
+
return { name: 'enter', ctrl: false, shift: false, meta: true, sequence };
|
|
214
|
+
}
|
|
114
215
|
if (!key) {
|
|
115
216
|
// 手动解析
|
|
116
217
|
if (char === '\r' || char === '\n')
|
|
@@ -127,6 +228,9 @@ function parseKey(char, key) {
|
|
|
127
228
|
if (key.name === 'return') {
|
|
128
229
|
key.name = 'enter';
|
|
129
230
|
}
|
|
231
|
+
if (key.name === 'enter' && (key.shift || key.meta)) {
|
|
232
|
+
return key;
|
|
233
|
+
}
|
|
130
234
|
// 如果 key.name 为空,使用 char 作为 name
|
|
131
235
|
if (!key.name && char) {
|
|
132
236
|
key.name = char;
|
|
@@ -135,6 +239,14 @@ function parseKey(char, key) {
|
|
|
135
239
|
}
|
|
136
240
|
function handleKeypress(char, key) {
|
|
137
241
|
const k = parseKey(char, key);
|
|
242
|
+
if (k.ctrl && k.name === 'l') {
|
|
243
|
+
clearTerminalView();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (k.ctrl && k.name === 'c') {
|
|
247
|
+
void handleCtrlC();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
138
250
|
// 命令面板模式
|
|
139
251
|
if ((0, command_panel_1.isPanelVisible)()) {
|
|
140
252
|
handlePanelKeypress(k, char);
|
|
@@ -145,6 +257,11 @@ function handleKeypress(char, key) {
|
|
|
145
257
|
handleFileCompletionKeypress(k, char);
|
|
146
258
|
return;
|
|
147
259
|
}
|
|
260
|
+
// Resume session picker mode
|
|
261
|
+
if (resumePickerState?.visible) {
|
|
262
|
+
handleResumePickerKeypress(k, char);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
148
265
|
// 历史导航模式
|
|
149
266
|
if (historyMode !== 'none') {
|
|
150
267
|
handleHistoryKeypress(k, char);
|
|
@@ -161,6 +278,13 @@ function handlePanelKeypress(k, char) {
|
|
|
161
278
|
case 'down':
|
|
162
279
|
(0, command_panel_1.navigatePanel)('down');
|
|
163
280
|
break;
|
|
281
|
+
case 'tab':
|
|
282
|
+
const completedCmd = (0, command_panel_1.completeSelectedCommand)();
|
|
283
|
+
if (completedCmd) {
|
|
284
|
+
currentInput = completedCmd;
|
|
285
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
164
288
|
case 'enter':
|
|
165
289
|
const cmd = (0, command_panel_1.selectCommand)();
|
|
166
290
|
if (cmd) {
|
|
@@ -310,16 +434,146 @@ function handleFileCompletionKeypress(k, char) {
|
|
|
310
434
|
}
|
|
311
435
|
}
|
|
312
436
|
}
|
|
437
|
+
function handleResumePickerKeypress(k, char) {
|
|
438
|
+
if (!resumePickerState?.visible)
|
|
439
|
+
return;
|
|
440
|
+
switch (k.name) {
|
|
441
|
+
case 'up':
|
|
442
|
+
resumePickerState.selectedIndex = Math.max(0, resumePickerState.selectedIndex - 1);
|
|
443
|
+
renderResumePicker();
|
|
444
|
+
break;
|
|
445
|
+
case 'down':
|
|
446
|
+
resumePickerState.selectedIndex = Math.min(resumePickerState.sessions.length - 1, resumePickerState.selectedIndex + 1);
|
|
447
|
+
renderResumePicker();
|
|
448
|
+
break;
|
|
449
|
+
case 'enter':
|
|
450
|
+
restoreSelectedResumeSession();
|
|
451
|
+
break;
|
|
452
|
+
case 'escape':
|
|
453
|
+
hideResumePicker();
|
|
454
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
455
|
+
break;
|
|
456
|
+
default:
|
|
457
|
+
if (char && /^[1-9]$/.test(char)) {
|
|
458
|
+
const index = Number(char) - 1;
|
|
459
|
+
if (index >= 0 && index < resumePickerState.sessions.length) {
|
|
460
|
+
resumePickerState.selectedIndex = index;
|
|
461
|
+
restoreSelectedResumeSession();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function showResumePicker(options) {
|
|
467
|
+
resumePickerState = {
|
|
468
|
+
visible: true,
|
|
469
|
+
sessions: options.sessions,
|
|
470
|
+
selectedIndex: 0,
|
|
471
|
+
title: options.title,
|
|
472
|
+
showProject: options.showProject,
|
|
473
|
+
moreCount: options.moreCount,
|
|
474
|
+
allProjects: options.allProjects,
|
|
475
|
+
};
|
|
476
|
+
renderResumePicker();
|
|
477
|
+
}
|
|
478
|
+
function renderResumePicker() {
|
|
479
|
+
if (!resumePickerState?.visible)
|
|
480
|
+
return;
|
|
481
|
+
clearResumePicker({ release: false });
|
|
482
|
+
const lines = (0, ui_v2_1.renderSessionPicker)({
|
|
483
|
+
title: resumePickerState.title,
|
|
484
|
+
sessions: resumePickerState.sessions,
|
|
485
|
+
selectedIndex: resumePickerState.selectedIndex,
|
|
486
|
+
width: process.stdout.columns || 80,
|
|
487
|
+
showProject: resumePickerState.showProject,
|
|
488
|
+
moreCount: resumePickerState.moreCount,
|
|
489
|
+
footer: ' ↑↓ Select Enter Resume 1-9 Quick Esc Cancel',
|
|
490
|
+
theme: {
|
|
491
|
+
accent: ACCENT,
|
|
492
|
+
dim: DIM,
|
|
493
|
+
selected: text => chalk_1.default.bgHex('#1E293B').hex('#E2E8F0')(text),
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
const offset = getResumePickerOffsetRows();
|
|
497
|
+
reserveResumePickerSpace(lines.length + offset);
|
|
498
|
+
resumePickerLines = lines;
|
|
499
|
+
process.stdout.write('\x1b7');
|
|
500
|
+
if (offset > 0) {
|
|
501
|
+
process.stdout.write(`\x1b[${offset}B\r`);
|
|
502
|
+
}
|
|
503
|
+
process.stdout.write('\x1b[J');
|
|
504
|
+
for (let index = 0; index < lines.length; index++) {
|
|
505
|
+
if (index > 0 || offset === 0) {
|
|
506
|
+
process.stdout.write('\n');
|
|
507
|
+
}
|
|
508
|
+
process.stdout.write('\r' + lines[index]);
|
|
509
|
+
}
|
|
510
|
+
process.stdout.write('\x1b8');
|
|
511
|
+
}
|
|
512
|
+
function hideResumePicker() {
|
|
513
|
+
clearResumePicker({ release: true });
|
|
514
|
+
resumePickerState = null;
|
|
515
|
+
}
|
|
516
|
+
function clearResumePicker(options = {}) {
|
|
517
|
+
const height = Math.max(resumePickerReservedHeight, resumePickerLines.length);
|
|
518
|
+
if (height <= 0)
|
|
519
|
+
return;
|
|
520
|
+
process.stdout.write('\x1b7');
|
|
521
|
+
const offset = getResumePickerOffsetRows();
|
|
522
|
+
if (offset > 0) {
|
|
523
|
+
process.stdout.write(`\x1b[${offset}B\r`);
|
|
524
|
+
}
|
|
525
|
+
process.stdout.write('\x1b[J');
|
|
526
|
+
process.stdout.write('\x1b8');
|
|
527
|
+
resumePickerLines = [];
|
|
528
|
+
if (options.release) {
|
|
529
|
+
resumePickerReservedHeight = 0;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function reserveResumePickerSpace(requiredHeight) {
|
|
533
|
+
if (requiredHeight <= resumePickerReservedHeight)
|
|
534
|
+
return;
|
|
535
|
+
const extraLines = requiredHeight - resumePickerReservedHeight;
|
|
536
|
+
process.stdout.write('\n'.repeat(extraLines));
|
|
537
|
+
process.stdout.write(`\x1b[${extraLines}A`);
|
|
538
|
+
resumePickerReservedHeight = requiredHeight;
|
|
539
|
+
}
|
|
540
|
+
function getResumePickerOffsetRows() {
|
|
541
|
+
return isV2UI() ? 2 : 1;
|
|
542
|
+
}
|
|
543
|
+
function restoreSelectedResumeSession() {
|
|
544
|
+
if (!resumePickerState?.visible)
|
|
545
|
+
return;
|
|
546
|
+
const session = resumePickerState.sessions[resumePickerState.selectedIndex];
|
|
547
|
+
const suffix = resumePickerState.allProjects ? ' --all' : '';
|
|
548
|
+
hideResumePicker();
|
|
549
|
+
currentInput = '';
|
|
550
|
+
(0, command_panel_1.clearRenderedInput)();
|
|
551
|
+
runInputTurn(`/resume ${session.id}${suffix}`).catch(err => {
|
|
552
|
+
console.log(ERROR(`Resume error: ${err.message || String(err)}`));
|
|
553
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
313
556
|
function handleNormalKeypress(k, char) {
|
|
314
557
|
switch (k.name) {
|
|
315
558
|
case 'enter':
|
|
559
|
+
if (k.shift || k.meta) {
|
|
560
|
+
if (isV2UI()) {
|
|
561
|
+
insertInputNewline();
|
|
562
|
+
}
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
316
565
|
// 多行模式:添加行
|
|
317
566
|
if ((0, multiline_input_1.isMultilineActive)()) {
|
|
318
567
|
if ((0, multiline_input_1.shouldEnterMultiline)(currentInput)) {
|
|
319
|
-
(0, multiline_input_1.addMultilineLine)(currentInput
|
|
568
|
+
(0, multiline_input_1.addMultilineLine)(currentInput);
|
|
320
569
|
currentInput = '';
|
|
321
|
-
|
|
322
|
-
|
|
570
|
+
if (isV2UI()) {
|
|
571
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
process.stdout.write('\r\x1b[2K');
|
|
575
|
+
process.stdout.write((0, multiline_input_1.renderContinuationPrompt)());
|
|
576
|
+
}
|
|
323
577
|
}
|
|
324
578
|
else {
|
|
325
579
|
// 结束多行,发送完整输入
|
|
@@ -337,8 +591,13 @@ function handleNormalKeypress(k, char) {
|
|
|
337
591
|
if ((0, multiline_input_1.shouldEnterMultiline)(currentInput)) {
|
|
338
592
|
(0, multiline_input_1.enterMultiline)(currentInput);
|
|
339
593
|
currentInput = '';
|
|
340
|
-
|
|
341
|
-
|
|
594
|
+
if (isV2UI()) {
|
|
595
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
process.stdout.write('\r\x1b[2K');
|
|
599
|
+
process.stdout.write((0, multiline_input_1.renderContinuationPrompt)());
|
|
600
|
+
}
|
|
342
601
|
return;
|
|
343
602
|
}
|
|
344
603
|
// 正常发送输入
|
|
@@ -392,7 +651,7 @@ function handleNormalKeypress(k, char) {
|
|
|
392
651
|
case 'd':
|
|
393
652
|
if (k.ctrl && currentInput === '') {
|
|
394
653
|
// Ctrl+D:退出(空输入时)
|
|
395
|
-
|
|
654
|
+
void shutdownCli();
|
|
396
655
|
}
|
|
397
656
|
else if (char) {
|
|
398
657
|
currentInput += char;
|
|
@@ -402,9 +661,8 @@ function handleNormalKeypress(k, char) {
|
|
|
402
661
|
case '/':
|
|
403
662
|
// Issue #30 fix: 只在输入为空时触发命令面板(即 `/` 是第一个字符)
|
|
404
663
|
// 避免在 URL(http://)、路径(src/)、正则等场景误触发
|
|
405
|
-
if (currentInput === '') {
|
|
664
|
+
if (currentInput === '' && !turnController.hasActiveTurn()) {
|
|
406
665
|
currentInput = '/';
|
|
407
|
-
(0, command_panel_1.resetRenderLength)();
|
|
408
666
|
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
409
667
|
(0, command_panel_1.showCommandPanel)('');
|
|
410
668
|
}
|
|
@@ -416,10 +674,25 @@ function handleNormalKeypress(k, char) {
|
|
|
416
674
|
break;
|
|
417
675
|
case '@':
|
|
418
676
|
// 显示文件补全
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
677
|
+
if (turnController.hasActiveTurn()) {
|
|
678
|
+
currentInput += '@';
|
|
679
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
const baseInput = currentInput;
|
|
683
|
+
currentInput += '@';
|
|
684
|
+
(0, file_completion_1.showFileCompletion)('', baseInput);
|
|
685
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
686
|
+
}
|
|
687
|
+
break;
|
|
688
|
+
case '?':
|
|
689
|
+
if (currentInput === '' && store.getSnapshot().config.ui?.renderer === 'v2' && !turnController.hasActiveTurn()) {
|
|
690
|
+
showV2Shortcuts();
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
currentInput += '?';
|
|
694
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
695
|
+
}
|
|
423
696
|
break;
|
|
424
697
|
default:
|
|
425
698
|
// 普通字符
|
|
@@ -429,6 +702,10 @@ function handleNormalKeypress(k, char) {
|
|
|
429
702
|
}
|
|
430
703
|
}
|
|
431
704
|
}
|
|
705
|
+
function insertInputNewline() {
|
|
706
|
+
currentInput += '\n';
|
|
707
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
708
|
+
}
|
|
432
709
|
function updateHistorySearch() {
|
|
433
710
|
if (searchQuery) {
|
|
434
711
|
const allHistory = (0, global_config_1.getInputHistory)();
|
|
@@ -444,10 +721,72 @@ function updateHistorySearch() {
|
|
|
444
721
|
(0, command_panel_1.redrawInputWithPrompt)(currentInput, `[Search: ${searchQuery}]`);
|
|
445
722
|
}
|
|
446
723
|
function showHistorySearchPrompt() {
|
|
447
|
-
|
|
448
|
-
|
|
724
|
+
(0, command_panel_1.clearRenderedInput)();
|
|
725
|
+
(0, command_panel_1.redrawInputWithPrompt)('', '[Search: ]');
|
|
449
726
|
}
|
|
450
|
-
|
|
727
|
+
function isV2UI() {
|
|
728
|
+
return store.getSnapshot().config.ui?.renderer === 'v2';
|
|
729
|
+
}
|
|
730
|
+
function showV2Shortcuts() {
|
|
731
|
+
console.log();
|
|
732
|
+
console.log((0, ui_v2_1.renderV2Shortcuts)(process.stdout.columns || 80));
|
|
733
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
734
|
+
}
|
|
735
|
+
function clearTerminalView() {
|
|
736
|
+
(0, command_panel_1.hideCommandPanel)();
|
|
737
|
+
(0, file_completion_1.hideFileCompletion)();
|
|
738
|
+
(0, command_panel_1.resetRenderLength)();
|
|
739
|
+
process.stdout.write('\x1Bc');
|
|
740
|
+
showBanner();
|
|
741
|
+
if (isV2UI()) {
|
|
742
|
+
console.log(DIM(' View cleared. Conversation context is preserved.'));
|
|
743
|
+
console.log((0, ui_v2_1.renderV2FooterHint)(process.stdout.columns || 80));
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
console.log(DIM(' View cleared. Conversation context is preserved.'));
|
|
747
|
+
}
|
|
748
|
+
console.log();
|
|
749
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
750
|
+
}
|
|
751
|
+
function cancelActiveInputMode() {
|
|
752
|
+
if ((0, multiline_input_1.isMultilineActive)()) {
|
|
753
|
+
(0, multiline_input_1.resetMultiline)();
|
|
754
|
+
currentInput = '';
|
|
755
|
+
console.log(DIM('(cancelled)'));
|
|
756
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
if ((0, command_panel_1.isPanelVisible)()) {
|
|
760
|
+
(0, command_panel_1.hideCommandPanel)();
|
|
761
|
+
(0, command_panel_1.clearPendingCommand)();
|
|
762
|
+
currentInput = '';
|
|
763
|
+
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
if ((0, file_completion_1.isFileCompletionVisible)()) {
|
|
767
|
+
(0, file_completion_1.hideFileCompletion)();
|
|
768
|
+
currentInput = (0, file_completion_1.getBaseInput)() + '@' + (0, file_completion_1.getFileQuery)();
|
|
769
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
if (resumePickerState?.visible) {
|
|
773
|
+
hideResumePicker();
|
|
774
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
if (historyMode !== 'none') {
|
|
778
|
+
historyMode = 'none';
|
|
779
|
+
searchQuery = '';
|
|
780
|
+
historyIndex = -1;
|
|
781
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
async function shutdownCli() {
|
|
787
|
+
if (isShuttingDown)
|
|
788
|
+
return;
|
|
789
|
+
isShuttingDown = true;
|
|
451
790
|
// 保存会话摘要后退出
|
|
452
791
|
if (currentSession) {
|
|
453
792
|
const messages = (0, session_storage_1.readSessionMessages)(currentSession.id);
|
|
@@ -468,18 +807,54 @@ async function handleCtrlC() {
|
|
|
468
807
|
console.log(SUCCESS('Goodbye! 🐴'));
|
|
469
808
|
process.exit(0);
|
|
470
809
|
}
|
|
810
|
+
async function handleCtrlC() {
|
|
811
|
+
if (cancelActiveInputMode()) {
|
|
812
|
+
turnController.clearExitIntent();
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (turnController.hasActiveTurn()) {
|
|
816
|
+
const shouldExit = turnController.registerExitIntent();
|
|
817
|
+
turnController.interruptActiveTurn();
|
|
818
|
+
if (shouldExit) {
|
|
819
|
+
await shutdownCli();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
(0, command_panel_1.writeLinePreservingInput)(DIM('Interrupted. Press Ctrl+C again to exit.'));
|
|
823
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (turnController.registerExitIntent()) {
|
|
827
|
+
await shutdownCli();
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
(0, command_panel_1.writeLinePreservingInput)(DIM('Press Ctrl+C again to exit.'));
|
|
831
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
832
|
+
}
|
|
471
833
|
// ============================================================================
|
|
472
834
|
// Banner
|
|
473
835
|
// ============================================================================
|
|
474
836
|
function showBanner() {
|
|
475
837
|
const config = store.getSnapshot().config;
|
|
476
838
|
const baseUrl = config.apiBaseUrl || '';
|
|
839
|
+
const provider = baseUrl.includes('anthropic') ? 'Anthropic'
|
|
840
|
+
: baseUrl.includes('openai') ? 'OpenAI'
|
|
841
|
+
: baseUrl.includes('dashscope') ? 'Alibaba Cloud'
|
|
842
|
+
: 'Custom';
|
|
843
|
+
if (config.ui?.renderer === 'v2') {
|
|
844
|
+
console.log();
|
|
845
|
+
console.log((0, ui_v2_1.renderV2ShellHeader)({
|
|
846
|
+
provider,
|
|
847
|
+
model: config.model,
|
|
848
|
+
projectPath: process.cwd(),
|
|
849
|
+
status: llm ? 'ready' : 'loading',
|
|
850
|
+
statusText: llm ? 'ready' : 'Set OPENHORSE_API_KEY',
|
|
851
|
+
version: VERSION,
|
|
852
|
+
}));
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
477
855
|
console.log();
|
|
478
856
|
console.log((0, box_1.renderHeaderBox)({
|
|
479
|
-
provider
|
|
480
|
-
: baseUrl.includes('openai') ? 'OpenAI'
|
|
481
|
-
: baseUrl.includes('dashscope') ? 'Alibaba Cloud'
|
|
482
|
-
: 'Custom',
|
|
857
|
+
provider,
|
|
483
858
|
model: config.model,
|
|
484
859
|
endpoint: baseUrl,
|
|
485
860
|
status: llm ? 'ready' : 'loading',
|
|
@@ -496,7 +871,9 @@ function getPrompt() {
|
|
|
496
871
|
const modeIndicator = modeText ? `[${modeText}] ` : '';
|
|
497
872
|
return ACCENT('❯ ') + DIM(modeIndicator);
|
|
498
873
|
}
|
|
499
|
-
async function handleInput(input) {
|
|
874
|
+
async function handleInput(input, abortSignal, options = {}) {
|
|
875
|
+
const redrawPrompt = options.redrawPrompt !== false;
|
|
876
|
+
const shouldUpdateStatus = options.updateStatus !== false;
|
|
500
877
|
const text = input.trim();
|
|
501
878
|
if (!text)
|
|
502
879
|
return;
|
|
@@ -509,13 +886,21 @@ async function handleInput(input) {
|
|
|
509
886
|
llm,
|
|
510
887
|
runtime,
|
|
511
888
|
sessionId: currentSession?.id,
|
|
889
|
+
ensureSession: ensureCurrentSession,
|
|
890
|
+
setSession: setCurrentSession,
|
|
891
|
+
getSession: getCurrentSession,
|
|
892
|
+
abortSignal,
|
|
893
|
+
writeOutput: command_panel_1.writeOutputPreservingInput,
|
|
894
|
+
writeLine: command_panel_1.writeLinePreservingInput,
|
|
512
895
|
};
|
|
896
|
+
let pendingSessionPicker;
|
|
513
897
|
try {
|
|
514
898
|
const parsed = (0, parser_1.parseInput)(text);
|
|
515
899
|
if (parsed.isCommand) {
|
|
516
900
|
const cmd = (0, commands_1.findCommand)(parsed.name);
|
|
517
901
|
if (cmd) {
|
|
518
902
|
const result = await cmd.execute(ctx, parsed.args);
|
|
903
|
+
pendingSessionPicker = result.sessionPicker;
|
|
519
904
|
// executeChat 会被 cmd.execute 调用,如果需要
|
|
520
905
|
if (!result.continueAsChat) {
|
|
521
906
|
// 命令完成后的输出已经在 cmd.execute 中处理
|
|
@@ -540,9 +925,16 @@ async function handleInput(input) {
|
|
|
540
925
|
console.log(ERROR(`Error: ${err.message || String(err)}`));
|
|
541
926
|
}
|
|
542
927
|
// 显示状态栏
|
|
543
|
-
|
|
928
|
+
if (shouldUpdateStatus) {
|
|
929
|
+
updateStatusBar();
|
|
930
|
+
}
|
|
544
931
|
// 重新显示 prompt
|
|
545
|
-
|
|
932
|
+
if (redrawPrompt) {
|
|
933
|
+
(0, command_panel_1.redrawInputWithPrompt)(currentInput);
|
|
934
|
+
}
|
|
935
|
+
if (pendingSessionPicker) {
|
|
936
|
+
showResumePicker(pendingSessionPicker);
|
|
937
|
+
}
|
|
546
938
|
}
|
|
547
939
|
/**
|
|
548
940
|
* 更新状态栏显示
|
|
@@ -563,7 +955,17 @@ function updateStatusBar() {
|
|
|
563
955
|
mcpTotal: mcpStatus.length,
|
|
564
956
|
};
|
|
565
957
|
// 在 prompt 上一行显示状态栏
|
|
566
|
-
|
|
958
|
+
if (snapshot.config.ui?.renderer === 'v2') {
|
|
959
|
+
(0, command_panel_1.setInputStatusText)((0, ui_v2_1.renderV2StatusBadge)({
|
|
960
|
+
...stats,
|
|
961
|
+
sessionId: currentSession?.id,
|
|
962
|
+
modeText: (0, types_1.getModeDisplayText)(snapshot.permissionMode) || undefined,
|
|
963
|
+
width: process.stdout.columns || 80,
|
|
964
|
+
}));
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
console.log((0, status_bar_1.renderStatusBar)(stats));
|
|
968
|
+
}
|
|
567
969
|
}
|
|
568
970
|
// 接口变量(用于兼容性,主要逻辑通过 keypress 处理)
|
|
569
971
|
let rl = null;
|
|
@@ -584,7 +986,13 @@ async function main() {
|
|
|
584
986
|
(0, config_dir_1.ensureConfigDir)();
|
|
585
987
|
(0, global_config_1.recordFirstStartTime)();
|
|
586
988
|
const projectPath = process.cwd();
|
|
587
|
-
const
|
|
989
|
+
const uiRenderer = parseCliUIRenderer(args);
|
|
990
|
+
const cliConfig = (0, config_1.loadConfig)(uiRenderer ? { ui: { renderer: uiRenderer } } : {});
|
|
991
|
+
(0, command_panel_1.setInputPromptRenderer)(cliConfig.ui?.renderer === 'v2' ? 'v2' : 'legacy');
|
|
992
|
+
(0, command_panel_1.setInputRenderContextProvider)(() => (cliConfig.ui?.renderer === 'v2' && (0, multiline_input_1.isMultilineActive)()
|
|
993
|
+
? { prefixLines: (0, multiline_input_1.getMultilineLines)() }
|
|
994
|
+
: {}));
|
|
995
|
+
(0, file_completion_1.setFileCompletionPromptRenderer)(cliConfig.ui?.renderer === 'v2' ? 'v2' : 'legacy');
|
|
588
996
|
// Load project memory
|
|
589
997
|
const memories = (0, storage_1.loadAllMemories)(projectPath);
|
|
590
998
|
const memoryContent = memories.length > 0
|
|
@@ -615,8 +1023,6 @@ async function main() {
|
|
|
615
1023
|
currentPlan: s.currentPlan,
|
|
616
1024
|
});
|
|
617
1025
|
});
|
|
618
|
-
currentSession = (0, session_storage_1.createSession)(projectPath, cliConfig.model);
|
|
619
|
-
(0, global_config_1.incrementSessionCount)();
|
|
620
1026
|
if ((0, config_1.isConfigured)(cliConfig)) {
|
|
621
1027
|
try {
|
|
622
1028
|
llm = new llm_1.LLMService({
|
|
@@ -656,6 +1062,10 @@ async function main() {
|
|
|
656
1062
|
// 提示
|
|
657
1063
|
console.log(SUCCESS('✔ System initialized'));
|
|
658
1064
|
console.log(DIM(' Type /help for commands, /exit to quit'));
|
|
1065
|
+
if (cliConfig.ui?.renderer === 'v2') {
|
|
1066
|
+
console.log(DIM(' UI v2 preview enabled: command palette and session picker'));
|
|
1067
|
+
console.log((0, ui_v2_1.renderV2FooterHint)(process.stdout.columns || 80));
|
|
1068
|
+
}
|
|
659
1069
|
if (!(0, config_1.isConfigured)(cliConfig)) {
|
|
660
1070
|
console.log(WARN(' ⚠ LLM not configured — set OPENHORSE_API_KEY'));
|
|
661
1071
|
}
|
|
@@ -687,6 +1097,9 @@ async function main() {
|
|
|
687
1097
|
}
|
|
688
1098
|
});
|
|
689
1099
|
// 初始 prompt
|
|
1100
|
+
if (cliConfig.ui?.renderer === 'v2') {
|
|
1101
|
+
updateStatusBar();
|
|
1102
|
+
}
|
|
690
1103
|
(0, command_panel_1.redrawInputWithPrompt)('');
|
|
691
1104
|
}
|
|
692
1105
|
main().catch(err => {
|