winter-super-cli 2026.6.5 → 2026.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/winter.js +1 -0
- package/package.json +3 -3
- package/src/agent/runtime.js +13 -16
- package/src/ai/model-capabilities.js +17 -1
- package/src/ai/prompts/system-prompt.js +33 -52
- package/src/ai/providers.js +179 -62
- package/src/ai/small-model-amplifier.js +7 -19
- package/src/cli/commands.js +162 -0
- package/src/cli/context-loader.js +1 -1
- package/src/cli/input-controller.js +55 -44
- package/src/cli/prompt-builder.js +20 -11
- package/src/cli/repl-commands.js +3 -0
- package/src/cli/repl.js +318 -444
- package/src/cli/slash-commands.js +1 -0
- package/src/cli/snowflake-logo.js +64 -86
- package/src/cli/terminal-ui.js +139 -85
- package/src/cli/tool-runtime.js +8 -3
- package/src/cli/tui.js +181 -0
- package/src/codebase-index/codegraph-adapter.js +154 -0
- package/src/codebase-index/indexer.js +1 -1
- package/src/codebase-index/search.js +31 -2
- package/src/context/router.js +4 -41
- package/src/context/token-juice.js +37 -10
- package/src/tools/executor.js +78 -3
package/src/cli/repl.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WINTER REPL
|
|
3
3
|
* Claude Code / Codex style interactive REPL
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import readline from 'readline';
|
|
7
7
|
import { promises as fs, watch as fsWatch } from 'fs';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
|
-
import {
|
|
9
|
+
import { colors, applyColorTheme, welcomeBanner } from './snowflake-logo.js';
|
|
10
10
|
import { renderBox, supportsUnicodeUi, terminalWidth, stripAnsi, wrapText, padVisible } from './terminal-ui.js';
|
|
11
|
+
import {
|
|
12
|
+
buildTuiSnapshot,
|
|
13
|
+
renderAssistantPanel,
|
|
14
|
+
renderConversationStartup,
|
|
15
|
+
renderLandingTui,
|
|
16
|
+
renderStartupTui,
|
|
17
|
+
renderStatusPanel,
|
|
18
|
+
} from './tui.js';
|
|
11
19
|
import { WinterInputController } from './input-controller.js';
|
|
12
20
|
import { ToolExecutor } from '../tools/executor.js';
|
|
13
21
|
import { SessionManager } from '../session/manager.js';
|
|
@@ -30,7 +38,7 @@ import {
|
|
|
30
38
|
buildPromptToolResultWithTokenJuice,
|
|
31
39
|
} from './tool-runtime.js';
|
|
32
40
|
import { TokenJuice } from '../context/token-juice.js';
|
|
33
|
-
import { classifyModelTier,
|
|
41
|
+
import { classifyModelTier, getModelBudgetMultiplier } from '../ai/model-capabilities.js';
|
|
34
42
|
import {
|
|
35
43
|
addUsage as mergeUsage,
|
|
36
44
|
buildToolCallSignature as buildToolCallSignatureText,
|
|
@@ -77,8 +85,8 @@ export class WinterREPL {
|
|
|
77
85
|
this.ai = new AIProviderManager(this.config);
|
|
78
86
|
this.tools = new ToolExecutor(this);
|
|
79
87
|
this.projectPath = options.projectPath || process.cwd();
|
|
80
|
-
this.sessionId = options.sessionId || null; //
|
|
81
|
-
this.version = options.version || '1.0.0'; //
|
|
88
|
+
this.sessionId = options.sessionId || null; // Nhận sessionId từ bin
|
|
89
|
+
this.version = options.version || '1.0.0'; // Nhận version từ bin
|
|
82
90
|
this.running = true;
|
|
83
91
|
this.history = [];
|
|
84
92
|
this.maxHistory = 500;
|
|
@@ -88,6 +96,7 @@ export class WinterREPL {
|
|
|
88
96
|
this.taskQueue = [];
|
|
89
97
|
this.isProcessing = false;
|
|
90
98
|
this.isCancelled = false;
|
|
99
|
+
this.currentAbortController = null;
|
|
91
100
|
this.sessionPermissionGrants = new Set();
|
|
92
101
|
this.permissionManager = new PermissionManager(this.config, this.session);
|
|
93
102
|
this.contextLoader = new ContextLoader({ projectPath: this.projectPath, session: this.session, tools: this.tools });
|
|
@@ -114,11 +123,13 @@ export class WinterREPL {
|
|
|
114
123
|
this.useUnicodeUi = supportsUnicodeUi();
|
|
115
124
|
this.inputController = new WinterInputController(this);
|
|
116
125
|
this.watchers = [];
|
|
126
|
+
this.startupNotices = [];
|
|
127
|
+
this._fixedPanel = Boolean(process.stdout.isTTY) && process.env.WINTER_FIXED_PANEL_TUI !== '0';
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
async initCodebaseSearch() {
|
|
120
131
|
if (this.codebaseSearcher) return;
|
|
121
|
-
this.codebaseSearcher = new CodebaseSearch({ projectPath: this.projectPath });
|
|
132
|
+
this.codebaseSearcher = new CodebaseSearch({ projectPath: this.projectPath, enableCodeGraph: true });
|
|
122
133
|
await this.codebaseSearcher.init();
|
|
123
134
|
this.atContext = new AtContextResolver({
|
|
124
135
|
projectPath: this.projectPath,
|
|
@@ -147,6 +158,15 @@ export class WinterREPL {
|
|
|
147
158
|
});
|
|
148
159
|
}
|
|
149
160
|
|
|
161
|
+
startupNotice(message) {
|
|
162
|
+
const text = String(message || '').trim();
|
|
163
|
+
if (!text) return;
|
|
164
|
+
this.startupNotices.push(text);
|
|
165
|
+
if (this.startupNotices.length > 6) {
|
|
166
|
+
this.startupNotices = this.startupNotices.slice(-6);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
150
170
|
async startCodebaseWatcher() {
|
|
151
171
|
if (this.codebaseWatcher) return;
|
|
152
172
|
await this.initCodebaseSearch();
|
|
@@ -166,7 +186,7 @@ export class WinterREPL {
|
|
|
166
186
|
await this.codebaseSearcher.clear();
|
|
167
187
|
}
|
|
168
188
|
const stats = await this.codebaseSearcher.reindex();
|
|
169
|
-
console.log(`${colors.green}
|
|
189
|
+
console.log(`${colors.green}✓ Codebase indexed:${colors.reset}`);
|
|
170
190
|
console.log(` ${colors.dim}Files: ${stats.totalFiles}, Chunks: ${stats.totalChunks}, Indexed: ${stats.indexedFiles}, Skipped: ${stats.skipped}${colors.reset}`);
|
|
171
191
|
}
|
|
172
192
|
|
|
@@ -229,17 +249,18 @@ export class WinterREPL {
|
|
|
229
249
|
if (before.totalChunks > 0) return before;
|
|
230
250
|
|
|
231
251
|
if (verbose) {
|
|
232
|
-
console.log(`${colors.dim}
|
|
252
|
+
console.log(`${colors.dim}Indexing codebase for semantic search...${colors.reset}`);
|
|
233
253
|
}
|
|
234
254
|
const indexedStats = await this.codebaseSearcher.reindex();
|
|
235
255
|
if (verbose) {
|
|
236
|
-
console.log(`${colors.green}
|
|
256
|
+
console.log(`${colors.green}✓ Codebase indexed: ${indexedStats.totalFiles} files, ${indexedStats.totalChunks} chunks${colors.reset}`);
|
|
237
257
|
}
|
|
238
258
|
return this.codebaseSearcher.indexer.getStats();
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
async buildCodebaseContext(task = '') {
|
|
242
262
|
try {
|
|
263
|
+
const modelTier = this.getActiveModelTier();
|
|
243
264
|
const stats = await this.ensureCodebaseIndex({ verbose: false });
|
|
244
265
|
if (!stats.totalChunks) return '';
|
|
245
266
|
|
|
@@ -278,7 +299,7 @@ export class WinterREPL {
|
|
|
278
299
|
}
|
|
279
300
|
}
|
|
280
301
|
|
|
281
|
-
return this.compactText(lines.join('\n'), this.
|
|
302
|
+
return this.compactText(lines.join('\n'), this.getCodebaseContextBudget(modelTier), 'codebase context');
|
|
282
303
|
} catch (error) {
|
|
283
304
|
return `[Codebase Index]\nUnavailable: ${error.message}`;
|
|
284
305
|
}
|
|
@@ -312,7 +333,7 @@ export class WinterREPL {
|
|
|
312
333
|
if (ans.trim().toLowerCase() === 'y') {
|
|
313
334
|
const ok = await this.diffView.restoreFromBackup(latest.backup, latest.original);
|
|
314
335
|
if (ok) {
|
|
315
|
-
console.log(`${colors.green}
|
|
336
|
+
console.log(`${colors.green}✓ Restored ${latest.original} from backup${colors.reset}`);
|
|
316
337
|
} else {
|
|
317
338
|
console.log(`${colors.red}Failed to restore${colors.reset}`);
|
|
318
339
|
}
|
|
@@ -326,18 +347,22 @@ export class WinterREPL {
|
|
|
326
347
|
toolName,
|
|
327
348
|
result,
|
|
328
349
|
compact: this.shouldUseCompactPrompt(),
|
|
350
|
+
modelTier: this.getActiveModelTier(),
|
|
329
351
|
compactText: (text, maxChars, label) => this.compactText(text, maxChars, label),
|
|
330
352
|
summarizeToolResult: value => this.tools?.summarizeToolResult?.(value) || { ...value },
|
|
331
353
|
});
|
|
332
354
|
}
|
|
333
355
|
|
|
334
356
|
async buildPromptToolResultForModel(toolName, result) {
|
|
357
|
+
const modelTier = this.getActiveModelTier();
|
|
358
|
+
const tokenJuice = this.getTokenJuiceForModelTier(modelTier);
|
|
335
359
|
return buildPromptToolResultWithTokenJuice({
|
|
336
360
|
toolName,
|
|
337
361
|
result,
|
|
338
362
|
projectPath: this.projectPath,
|
|
339
|
-
tokenJuice
|
|
363
|
+
tokenJuice,
|
|
340
364
|
compact: this.shouldUseCompactPrompt(),
|
|
365
|
+
modelTier,
|
|
341
366
|
compactText: (text, maxChars, label) => this.compactText(text, maxChars, label),
|
|
342
367
|
summarizeToolResult: value => this.tools?.summarizeToolResult?.(value) || { ...value },
|
|
343
368
|
});
|
|
@@ -456,7 +481,7 @@ export class WinterREPL {
|
|
|
456
481
|
});
|
|
457
482
|
await this.session.replaceMemory('[Project Anchor]', `Current project is ${this.projectPath}. Treat this path as the canonical working directory for the session.`, 'info');
|
|
458
483
|
|
|
459
|
-
//
|
|
484
|
+
// Tự động đọc và ghi nhớ một số tài nguyên cục bộ an toàn.
|
|
460
485
|
const fsPromises = await import('fs/promises');
|
|
461
486
|
const resourcePaths = this.getResourcePaths();
|
|
462
487
|
const autoLoadTargets = [resourcePaths.agents, resourcePaths.designs, resourcePaths.karpathy, resourcePaths.pageAgent];
|
|
@@ -469,9 +494,9 @@ export class WinterREPL {
|
|
|
469
494
|
if (stat.isFile()) {
|
|
470
495
|
const content = await fsPromises.readFile(targetPath, 'utf8');
|
|
471
496
|
const fileName = path.basename(targetPath);
|
|
472
|
-
const memoryKey = `[
|
|
497
|
+
const memoryKey = `[Tự động ghi nhớ file ${fileName}]`;
|
|
473
498
|
await this.session.replaceMemory(memoryKey, content);
|
|
474
|
-
|
|
499
|
+
this.startupNotice(`loaded ${fileName}`);
|
|
475
500
|
continue;
|
|
476
501
|
}
|
|
477
502
|
|
|
@@ -485,9 +510,9 @@ export class WinterREPL {
|
|
|
485
510
|
const cstat = await fsPromises.stat(p).catch(() => null);
|
|
486
511
|
if (cstat && cstat.isFile()) {
|
|
487
512
|
const content = await fsPromises.readFile(p, 'utf8');
|
|
488
|
-
const memoryKey = `[
|
|
513
|
+
const memoryKey = `[Tự động ghi nhớ file ${path.basename(targetPath)}/${c}]`;
|
|
489
514
|
await this.session.replaceMemory(memoryKey, content);
|
|
490
|
-
|
|
515
|
+
this.startupNotice(`loaded ${path.basename(targetPath)}/${c}`);
|
|
491
516
|
loaded = true;
|
|
492
517
|
break;
|
|
493
518
|
}
|
|
@@ -505,63 +530,63 @@ export class WinterREPL {
|
|
|
505
530
|
}
|
|
506
531
|
}
|
|
507
532
|
|
|
508
|
-
//
|
|
533
|
+
// Nạp các file quy tắc dự án theo thứ tự ưu tiên.
|
|
509
534
|
const projectInstructionFiles = await this.readProjectInstructionFiles();
|
|
510
535
|
try {
|
|
511
536
|
if (projectInstructionFiles.length > 0) {
|
|
512
537
|
for (const file of projectInstructionFiles) {
|
|
513
|
-
const memoryKey = `[Quy
|
|
538
|
+
const memoryKey = `[Quy tắc dự án từ ${file.relativePath}]`;
|
|
514
539
|
await this.session.replaceMemory(memoryKey, file.content);
|
|
515
|
-
|
|
540
|
+
this.startupNotice(`rules ${file.relativePath}`);
|
|
516
541
|
}
|
|
517
542
|
}
|
|
518
543
|
} catch (e) {
|
|
519
|
-
//
|
|
544
|
+
// Nếu không có, tự động tạo file mẫu.
|
|
520
545
|
const template = `# Winter Project Rules
|
|
521
546
|
|
|
522
|
-
##
|
|
523
|
-
- **Name**: [
|
|
524
|
-
- **Description**: [
|
|
547
|
+
## Project Overview
|
|
548
|
+
- **Name**: [Tên dự án]
|
|
549
|
+
- **Description**: [Mô tả ngắn về dự án]
|
|
525
550
|
|
|
526
|
-
##
|
|
551
|
+
## Tech Stack
|
|
527
552
|
- **Languages**: JavaScript / TypeScript
|
|
528
553
|
- **Runtime**: Node.js
|
|
529
|
-
- **Frameworks**: [
|
|
554
|
+
- **Frameworks**: [Tự điền nếu có, VD: Express, React...]
|
|
530
555
|
|
|
531
|
-
##
|
|
556
|
+
## AI Behavior & Coding Guidelines
|
|
532
557
|
|
|
533
|
-
### 1.
|
|
534
|
-
-
|
|
535
|
-
-
|
|
536
|
-
-
|
|
558
|
+
### 1. Nguyên tắc Code (Coding Standards)
|
|
559
|
+
- Luôn ưu tiên viết code sạch (Clean Code), dễ đọc và dễ bảo trì.
|
|
560
|
+
- Sử dụng ES Modules (\`import/export\`) thay vì CommonJS (\`require\`) trừ khi có lý do đặc biệt.
|
|
561
|
+
- Giữ nguyên các comment và JSDoc hiện có trong file trừ khi được yêu cầu sửa.
|
|
537
562
|
|
|
538
|
-
### 2.
|
|
539
|
-
-
|
|
540
|
-
- Khi
|
|
541
|
-
-
|
|
563
|
+
### 2. Tương tác với người dùng (User Interaction)
|
|
564
|
+
- Luôn giải thích ngắn gọn lý do thực hiện thay đổi trước khi sửa file.
|
|
565
|
+
- Khi gặp lỗi, hãy đề xuất giải pháp thay vì chỉ báo lỗi.
|
|
566
|
+
- Không tự tiện xóa code cũ của user trừ khi chắc chắn không còn dùng hoặc được yêu cầu.
|
|
542
567
|
|
|
543
568
|
### 3. Git & Commits
|
|
544
|
-
-
|
|
545
|
-
-
|
|
569
|
+
- Viết commit message theo chuẩn Conventional Commits (VD: \`feat:\`, \`fix:\`, \`docs:\`).
|
|
570
|
+
- Luôn kiểm tra \`git status\` trước khi thực hiện thay đổi lớn.
|
|
546
571
|
|
|
547
|
-
### 4.
|
|
548
|
-
-
|
|
549
|
-
-
|
|
572
|
+
### 4. Xử lý File (File Operations)
|
|
573
|
+
- Chỉ sửa những dòng cần thiết, tránh viết lại toàn bộ file nếu không cần.
|
|
574
|
+
- Luôn đảm bảo file không bị lỗi cú pháp sau khi sửa.
|
|
550
575
|
`;
|
|
551
576
|
try {
|
|
552
577
|
const projectWinterMd = path.join(this.projectPath, 'winter.md');
|
|
553
578
|
await fsPromises.writeFile(projectWinterMd, template, 'utf8');
|
|
554
|
-
|
|
555
|
-
|
|
579
|
+
this.startupNotice('created winter.md');
|
|
580
|
+
|
|
556
581
|
|
|
557
|
-
//
|
|
558
|
-
await this.session.replaceMemory(`[Quy
|
|
582
|
+
// Nạp luôn vào memory.
|
|
583
|
+
await this.session.replaceMemory(`[Quy tắc dự án từ winter.md]`, template);
|
|
559
584
|
} catch (err) {
|
|
560
|
-
//
|
|
585
|
+
// Bỏ qua nếu không ghi được file.
|
|
561
586
|
}
|
|
562
587
|
}
|
|
563
588
|
|
|
564
|
-
//
|
|
589
|
+
// Tự động tạo design.md, skill.md, rule.md nếu chưa có.
|
|
565
590
|
const autoCreateDocs = await buildProjectDocs({
|
|
566
591
|
projectPath: this.projectPath,
|
|
567
592
|
resourcePaths: this.getResourcePaths(),
|
|
@@ -577,17 +602,17 @@ export class WinterREPL {
|
|
|
577
602
|
if (!isWinterGeneratedProjectDoc(existing)) continue;
|
|
578
603
|
|
|
579
604
|
await fsPromises.writeFile(filePath, doc.content, 'utf8');
|
|
580
|
-
|
|
581
|
-
const memoryKey = `[Quy
|
|
605
|
+
this.startupNotice(`updated ${doc.filename}`);
|
|
606
|
+
const memoryKey = `[Quy tắc dự án từ ${doc.filename}]`;
|
|
582
607
|
await this.session.replaceMemory(memoryKey, doc.content);
|
|
583
608
|
} catch {
|
|
584
609
|
try {
|
|
585
610
|
await fsPromises.writeFile(filePath, doc.content, 'utf8');
|
|
586
|
-
|
|
587
|
-
const memoryKey = `[Quy
|
|
611
|
+
this.startupNotice(`created ${doc.filename}`);
|
|
612
|
+
const memoryKey = `[Quy tắc dự án từ ${doc.filename}]`;
|
|
588
613
|
await this.session.replaceMemory(memoryKey, doc.content);
|
|
589
614
|
} catch (err) {
|
|
590
|
-
//
|
|
615
|
+
// Bỏ qua nếu không tạo được.
|
|
591
616
|
}
|
|
592
617
|
}
|
|
593
618
|
}
|
|
@@ -596,61 +621,29 @@ export class WinterREPL {
|
|
|
596
621
|
await this.compactStartupMemories({ projectInstructionFiles, autoCreateDocs });
|
|
597
622
|
|
|
598
623
|
// Codebase Index: warm in background, then inject summaries into model context on demand.
|
|
599
|
-
this.codebaseWarmup = this.ensureCodebaseIndex({ verbose:
|
|
624
|
+
this.codebaseWarmup = this.ensureCodebaseIndex({ verbose: false })
|
|
600
625
|
.then(() => this.startCodebaseWatcher())
|
|
601
626
|
.catch((error) => {
|
|
602
|
-
|
|
627
|
+
this.startupNotice(`codebase disabled: ${error.message}`);
|
|
603
628
|
});
|
|
604
629
|
|
|
605
|
-
const activeProvider = this.ai.getActiveProvider();
|
|
606
|
-
const info = {
|
|
607
|
-
project: this.projectPath,
|
|
608
|
-
provider: activeProvider,
|
|
609
|
-
model: this.ai.providers[activeProvider]?.model,
|
|
610
|
-
session: this.session.getSessionId().substring(0, 8)
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
// Show banner only if not already shown
|
|
614
|
-
if (!process.env.WINTER_BANNER_SHOWN) {
|
|
615
|
-
console.log(welcomeBanner(this.version, info));
|
|
616
|
-
this.showCommandMenu();
|
|
617
|
-
process.env.WINTER_BANNER_SHOWN = '1';
|
|
618
|
-
} else {
|
|
619
|
-
this.showStatus();
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Hi?n th? l?ch s? chat n?u ?ang load l?i session c?, nh?ng ch? replay b?n t?m t?t.
|
|
623
630
|
const sessionHistory = this.session.getHistory(4);
|
|
624
631
|
if (sessionHistory.length > 0) {
|
|
625
|
-
|
|
626
|
-
const W = Math.max(60, Math.min(Math.floor(columns * 0.95), 100));
|
|
627
|
-
const titleStr = ' L?ch s? phi?n l?m vi?c ';
|
|
628
|
-
const ruleChar = this.useUnicodeUi ? '?' : '-';
|
|
629
|
-
const sideLine = ruleChar.repeat(Math.max(0, Math.floor((W - titleStr.length) / 2)));
|
|
630
|
-
const bottomLine = ruleChar.repeat(W);
|
|
631
|
-
|
|
632
|
-
console.log(`\n${colors.dim}${sideLine}${titleStr}${sideLine}${colors.reset}`);
|
|
633
|
-
for (const msg of sessionHistory) {
|
|
634
|
-
const text = this.formatStartupHistoryEntry(msg.content);
|
|
635
|
-
if (msg.role === 'user') {
|
|
636
|
-
console.log(`\n${colors.cyan}B?n:${colors.reset} ${text}`);
|
|
637
|
-
} else if (msg.role === 'assistant') {
|
|
638
|
-
console.log(`\n${colors.bright}${colors.magenta}Winter:${colors.reset} ${text}`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
console.log(`\n${colors.dim}${bottomLine}${colors.reset}\n`);
|
|
632
|
+
this.startupNotice(`${sessionHistory.length} recent messages`);
|
|
642
633
|
}
|
|
643
634
|
|
|
635
|
+
this.showStatus();
|
|
636
|
+
|
|
644
637
|
// Setup readline
|
|
645
638
|
this.rl = readline.createInterface({
|
|
646
639
|
input: process.stdin,
|
|
647
640
|
output: process.stdout,
|
|
648
|
-
prompt: `${colors.bright}${colors.cyan}
|
|
641
|
+
prompt: `${colors.bright}${colors.cyan}winter > ${colors.reset}`,
|
|
649
642
|
completer: this.completer.bind(this),
|
|
650
643
|
});
|
|
651
644
|
this.inputController.installSlashSuggestions();
|
|
652
645
|
|
|
653
|
-
//
|
|
646
|
+
// Bắt sự kiện Ctrl+C để in ra lệnh tiếp tục session.
|
|
654
647
|
this.rl.on('SIGINT', () => {
|
|
655
648
|
console.log(`\n\n${colors.cyan}Cảm ơn đã sử dụng Winter!${colors.reset}`);
|
|
656
649
|
console.log(`${colors.yellow}Tiếp tục phiên làm việc:${colors.reset}`);
|
|
@@ -658,7 +651,7 @@ export class WinterREPL {
|
|
|
658
651
|
process.exit(0);
|
|
659
652
|
});
|
|
660
653
|
|
|
661
|
-
//
|
|
654
|
+
// Hiển thị prompt lần đầu tiên ngay khi khởi động xong.
|
|
662
655
|
this.showInputPrompt();
|
|
663
656
|
|
|
664
657
|
this.rl.on('line', (line) => {
|
|
@@ -695,7 +688,7 @@ export class WinterREPL {
|
|
|
695
688
|
if (!this.useUnicodeUi) {
|
|
696
689
|
text = text
|
|
697
690
|
.replace(/\p{Extended_Pictographic}/gu, '')
|
|
698
|
-
.replace(/[
|
|
691
|
+
.replace(/[─━—–]/g, '-')
|
|
699
692
|
.replace(/\s+/g, ' ')
|
|
700
693
|
.trim();
|
|
701
694
|
}
|
|
@@ -711,16 +704,39 @@ export class WinterREPL {
|
|
|
711
704
|
return this.inputController.closeInputBox();
|
|
712
705
|
}
|
|
713
706
|
|
|
707
|
+
closeSlashMenu() {
|
|
708
|
+
return this.inputController.closeSlashMenu();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
handleSlashMenuKey(key = {}) {
|
|
712
|
+
return this.inputController.handleSlashMenuKey(key);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
handleDirectClipboardPaste() {
|
|
716
|
+
return this.inputController.handleDirectClipboardPaste();
|
|
717
|
+
}
|
|
718
|
+
|
|
714
719
|
buildInputPanel() {
|
|
715
720
|
return this.inputController.buildInputPanel();
|
|
716
721
|
}
|
|
717
722
|
|
|
718
723
|
showStatus() {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
+
const snapshot = buildTuiSnapshot(this);
|
|
725
|
+
if (process.stdout.isTTY) {
|
|
726
|
+
console.log(`\n${welcomeBanner(this.version, {
|
|
727
|
+
project: snapshot.projectPath,
|
|
728
|
+
session: snapshot.sessionShort,
|
|
729
|
+
provider: snapshot.provider,
|
|
730
|
+
model: snapshot.model,
|
|
731
|
+
})}\n`);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
console.log(`\n${renderConversationStartup(snapshot, { colors })}\n`);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
showTuiDashboard() {
|
|
738
|
+
const snapshot = buildTuiSnapshot(this);
|
|
739
|
+
console.log(`\n${renderLandingTui(snapshot, { colors, title: 'Winter Dashboard' })}\n`);
|
|
724
740
|
}
|
|
725
741
|
|
|
726
742
|
getResourcePaths() {
|
|
@@ -917,7 +933,8 @@ export class WinterREPL {
|
|
|
917
933
|
async handleInput(input) {
|
|
918
934
|
if (this.isProcessing) {
|
|
919
935
|
const pos = this.taskQueue.length + 1;
|
|
920
|
-
|
|
936
|
+
const preview = input.length > 40 ? input.slice(0, 37) + '...' : input;
|
|
937
|
+
console.log(`${colors.yellow}⧗${colors.reset} ${colors.bright}Queued #${pos}${colors.reset} ${colors.dim}› ${preview}${colors.reset}`);
|
|
921
938
|
this.taskQueue.push(input);
|
|
922
939
|
return;
|
|
923
940
|
}
|
|
@@ -927,7 +944,9 @@ export class WinterREPL {
|
|
|
927
944
|
async processInputTask(input) {
|
|
928
945
|
this.isProcessing = true;
|
|
929
946
|
this.isCancelled = false;
|
|
947
|
+
this.currentAbortController = new AbortController();
|
|
930
948
|
try {
|
|
949
|
+
this.closeSlashMenu();
|
|
931
950
|
this.history.push(input);
|
|
932
951
|
if (this.history.length > this.maxHistory) {
|
|
933
952
|
this.history = this.history.slice(-this.maxHistory);
|
|
@@ -998,13 +1017,16 @@ export class WinterREPL {
|
|
|
998
1017
|
}
|
|
999
1018
|
} catch (error) {
|
|
1000
1019
|
if (error.message === 'AbortError') {
|
|
1001
|
-
|
|
1020
|
+
if (!this.isCancelled) {
|
|
1021
|
+
console.log(colors.red + '\nĐã hủy công việc hiện tại.' + colors.reset);
|
|
1022
|
+
}
|
|
1002
1023
|
} else {
|
|
1003
|
-
console.log(colors.red + '\
|
|
1024
|
+
console.log(colors.red + '\nLỗi: ' + error.message + colors.reset);
|
|
1004
1025
|
}
|
|
1005
1026
|
} finally {
|
|
1006
1027
|
this.isProcessing = false;
|
|
1007
1028
|
this.isCancelled = false;
|
|
1029
|
+
this.currentAbortController = null;
|
|
1008
1030
|
if (this.spinner) this.spinner.stop();
|
|
1009
1031
|
|
|
1010
1032
|
if (this.taskQueue.length > 0) {
|
|
@@ -1063,13 +1085,13 @@ export class WinterREPL {
|
|
|
1063
1085
|
|
|
1064
1086
|
let printedLines = 0;
|
|
1065
1087
|
const render = () => {
|
|
1066
|
-
//
|
|
1088
|
+
// Xóa những dòng đã in trước đó.
|
|
1067
1089
|
if (printedLines > 0) {
|
|
1068
1090
|
process.stdout.write('\x1b[' + printedLines + 'A\x1b[J');
|
|
1069
1091
|
}
|
|
1070
1092
|
|
|
1071
1093
|
let out = `\n[36m${title}[0m\n`;
|
|
1072
|
-
out += `[2mD
|
|
1094
|
+
out += `[2mDùng mũi tên (↑/↓) để di chuyển, [Space] để chọn/bỏ chọn, [Enter] để xác nhận[0m\n\n`;
|
|
1073
1095
|
|
|
1074
1096
|
for (let i = 0; i < items.length; i++) {
|
|
1075
1097
|
const isHover = i === cursor;
|
|
@@ -1119,11 +1141,11 @@ export class WinterREPL {
|
|
|
1119
1141
|
}
|
|
1120
1142
|
|
|
1121
1143
|
async generateInteractivePlan(task) {
|
|
1122
|
-
this.spinner = new Spinner('
|
|
1144
|
+
this.spinner = new Spinner('Đang phân tích và chia nhỏ yêu cầu...');
|
|
1123
1145
|
this.spinner.start();
|
|
1124
1146
|
|
|
1125
1147
|
const messages = [
|
|
1126
|
-
{ role: 'system', content: '
|
|
1148
|
+
{ role: 'system', content: 'Bạn là chuyên gia lập kế hoạch. Hãy chia nhỏ yêu cầu của người dùng thành các bước cụ thể, hành động được, rất ngắn gọn (dưới 15 chữ mỗi bước). Chỉ trả về một mảng JSON các chuỗi, không giải thích gì thêm. Ví dụ: ["Tạo file index.html", "Thêm CSS styling", "Viết script.js"]' },
|
|
1127
1149
|
{ role: 'user', content: task }
|
|
1128
1150
|
];
|
|
1129
1151
|
|
|
@@ -1146,40 +1168,40 @@ export class WinterREPL {
|
|
|
1146
1168
|
}
|
|
1147
1169
|
|
|
1148
1170
|
if (!Array.isArray(items) || items.length === 0) {
|
|
1149
|
-
console.log(`\x1b[33mKh
|
|
1171
|
+
console.log(`\x1b[33mKhông thể parse kế hoạch. AI phản hồi: ${text}\x1b[0m`);
|
|
1150
1172
|
return;
|
|
1151
1173
|
}
|
|
1152
1174
|
|
|
1153
|
-
const selectedSteps = await this.showInteractiveChecklist('
|
|
1175
|
+
const selectedSteps = await this.showInteractiveChecklist('KẾ HOẠCH THỰC HIỆN:', items);
|
|
1154
1176
|
|
|
1155
1177
|
if (selectedSteps.length > 0) {
|
|
1156
1178
|
for (const step of selectedSteps) {
|
|
1157
1179
|
await this.session.createPlan(step, task);
|
|
1158
1180
|
}
|
|
1159
|
-
console.log(`\x1b[32m
|
|
1181
|
+
console.log(`\x1b[32m✓ Đã thêm ${selectedSteps.length} công việc vào Memory (gõ /plans để xem).\x1b[0m`);
|
|
1160
1182
|
|
|
1161
|
-
this.rl.question(`\n\x1b[36mB
|
|
1183
|
+
this.rl.question(`\n\x1b[36mBạn có muốn AI bắt tay làm công việc ĐẦU TIÊN ngay bây giờ không? [y/N]: \x1b[0m`, async (answer) => {
|
|
1162
1184
|
if (answer.toLowerCase() === 'y') {
|
|
1163
|
-
await this.chat(`
|
|
1185
|
+
await this.chat(`Hãy bắt đầu thực hiện bước đầu tiên: ${selectedSteps[0]}`);
|
|
1164
1186
|
}
|
|
1165
1187
|
});
|
|
1166
1188
|
} else {
|
|
1167
|
-
console.log(`\n\x1b[2m
|
|
1189
|
+
console.log(`\n\x1b[2mĐã huỷ kế hoạch.\x1b[0m`);
|
|
1168
1190
|
}
|
|
1169
1191
|
} catch (e) {
|
|
1170
1192
|
if (this.spinner) this.spinner.stop();
|
|
1171
|
-
console.log(`\x1b[31mL
|
|
1193
|
+
console.log(`\x1b[31mLỗi: ${e.message}\x1b[0m`);
|
|
1172
1194
|
}
|
|
1173
1195
|
}
|
|
1174
1196
|
|
|
1175
1197
|
|
|
1176
1198
|
async runAutoHealing(task) {
|
|
1177
|
-
console.log(`\n\x1b[35m[ TDD AUTO-HEALING MODE ]\x1b[0m
|
|
1199
|
+
console.log(`\n\x1b[35m[ TDD AUTO-HEALING MODE ]\x1b[0m Kích hoạt vòng lặp tự sửa lỗi.`);
|
|
1178
1200
|
|
|
1179
|
-
//
|
|
1201
|
+
// Tăng giới hạn loop lên 15 để cho phép AI tự sửa lỗi nhiều lần.
|
|
1180
1202
|
const originalRequestAssistantTurn = this.requestAssistantTurn;
|
|
1181
1203
|
|
|
1182
|
-
//
|
|
1204
|
+
// Chèn prompt đặc biệt ép AI phải verify.
|
|
1183
1205
|
const verifyCommands = await this.inferVerificationCommands(task);
|
|
1184
1206
|
const autoPrompt = `TASK: ${task}
|
|
1185
1207
|
|
|
@@ -1195,7 +1217,7 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1195
1217
|
}
|
|
1196
1218
|
|
|
1197
1219
|
async runAutoCommit(context = '') {
|
|
1198
|
-
console.log(`\n${colors.cyan}
|
|
1220
|
+
console.log(`\n${colors.cyan}✓ Tạo commit message tự động...${colors.reset}`);
|
|
1199
1221
|
const diffResult = await this.tools.execute('Bash', { command: 'git diff --cached' }, { cwd: this.projectPath });
|
|
1200
1222
|
|
|
1201
1223
|
let diff = diffResult.stdout;
|
|
@@ -1207,14 +1229,14 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1207
1229
|
}
|
|
1208
1230
|
|
|
1209
1231
|
if (!diff || diff.trim() === '') {
|
|
1210
|
-
console.log(`${colors.yellow}
|
|
1232
|
+
console.log(`${colors.yellow}Không có thay đổi nào để commit.${colors.reset}`);
|
|
1211
1233
|
if (this.running && !this.readlineClosed) this.rl.prompt();
|
|
1212
1234
|
return;
|
|
1213
1235
|
}
|
|
1214
1236
|
|
|
1215
|
-
const prompt = `
|
|
1237
|
+
const prompt = `Bạn là chuyên gia Git. Hãy viết MỘT commit message duy nhất (không giải thích thêm) cho những thay đổi sau đây. Dùng format chuẩn Conventional Commits (feat/fix/chore/refactor: ...). ${context ? `\nNgữ cảnh: ${context}` : ''}\n\nDiff:\n${diff.slice(0, 6000)}`;
|
|
1216
1238
|
|
|
1217
|
-
this.spinner = new Spinner('
|
|
1239
|
+
this.spinner = new Spinner('Đang sinh commit message...');
|
|
1218
1240
|
this.spinner.start();
|
|
1219
1241
|
try {
|
|
1220
1242
|
const response = await this.ai.sendRequest([{ role: 'user', content: prompt }], {
|
|
@@ -1222,19 +1244,19 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1222
1244
|
});
|
|
1223
1245
|
if (this.spinner) this.spinner.stop();
|
|
1224
1246
|
|
|
1225
|
-
const message = response.choices?.[0]?.message?.content?.trim().replace(/^["']|["']$/g, '') || '
|
|
1247
|
+
const message = response.choices?.[0]?.message?.content?.trim().replace(/^["']|["']$/g, '') || 'Cập nhật mã nguồn';
|
|
1226
1248
|
|
|
1227
|
-
console.log(`\n${colors.green}
|
|
1249
|
+
console.log(`\n${colors.green}Đề xuất commit message:${colors.reset}`);
|
|
1228
1250
|
console.log(`${colors.bright}${message}${colors.reset}\n`);
|
|
1229
1251
|
|
|
1230
|
-
this.rl.question(`${colors.yellow}
|
|
1252
|
+
this.rl.question(`${colors.yellow}Bạn có muốn commit với message này không? [y/N/e (tự sửa)]: ${colors.reset}`, async (ans) => {
|
|
1231
1253
|
const choice = ans.trim().toLowerCase();
|
|
1232
1254
|
if (choice === 'y') {
|
|
1233
1255
|
if (!isStaged) await this.tools.execute('Bash', { command: 'git add .' }, { cwd: this.projectPath });
|
|
1234
1256
|
const res = await this.tools.execute('Bash', { command: `git commit -m "${message.replace(/"/g, '\\"')}"` }, { cwd: this.projectPath });
|
|
1235
1257
|
console.log(res.stdout);
|
|
1236
1258
|
} else if (choice === 'e') {
|
|
1237
|
-
this.rl.question(`${colors.cyan}
|
|
1259
|
+
this.rl.question(`${colors.cyan}Nhập commit message: ${colors.reset}`, async (customMsg) => {
|
|
1238
1260
|
if (customMsg.trim()) {
|
|
1239
1261
|
if (!isStaged) await this.tools.execute('Bash', { command: 'git add .' }, { cwd: this.projectPath });
|
|
1240
1262
|
const res = await this.tools.execute('Bash', { command: `git commit -m "${customMsg.replace(/"/g, '\\"')}"` }, { cwd: this.projectPath });
|
|
@@ -1244,28 +1266,28 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1244
1266
|
});
|
|
1245
1267
|
return;
|
|
1246
1268
|
} else {
|
|
1247
|
-
console.log(`${colors.dim}
|
|
1269
|
+
console.log(`${colors.dim}Đã huỷ commit.${colors.reset}`);
|
|
1248
1270
|
}
|
|
1249
1271
|
if (this.running && !this.readlineClosed) this.rl.prompt();
|
|
1250
1272
|
});
|
|
1251
1273
|
} catch (e) {
|
|
1252
1274
|
if (this.spinner) this.spinner.stop();
|
|
1253
|
-
console.log(`${colors.red}
|
|
1275
|
+
console.log(`${colors.red}Lỗi: ${e.message}${colors.reset}`);
|
|
1254
1276
|
if (this.running && !this.readlineClosed) this.rl.prompt();
|
|
1255
1277
|
}
|
|
1256
1278
|
}
|
|
1257
1279
|
|
|
1258
1280
|
async runCodeReview(context = '') {
|
|
1259
|
-
console.log(`\n${colors.cyan}
|
|
1281
|
+
console.log(`\n${colors.cyan}AI đang soi code của bạn...${colors.reset}`);
|
|
1260
1282
|
const diffResult = await this.tools.execute('Bash', { command: 'git diff HEAD' }, { cwd: this.projectPath });
|
|
1261
1283
|
const diff = diffResult.stdout;
|
|
1262
1284
|
|
|
1263
1285
|
if (!diff || diff.trim() === '') {
|
|
1264
|
-
console.log(`${colors.yellow}
|
|
1286
|
+
console.log(`${colors.yellow}Không có thay đổi nào để review.${colors.reset}`);
|
|
1265
1287
|
return;
|
|
1266
1288
|
}
|
|
1267
1289
|
|
|
1268
|
-
const prompt = `
|
|
1290
|
+
const prompt = `Hãy đóng vai một Senior Developer khó tính. Review nhanh các thay đổi sau đây. Chỉ ra bug nếu có, vấn đề bảo mật, hoặc những chỗ cần clean code. Không cần khen ngợi, hãy nói thẳng vào vấn đề. Trình bày dạng Markdown đẹp mắt.\n\n${context ? `Ngữ cảnh: ${context}\n` : ''}Diff:\n${diff.slice(0, 8000)}`;
|
|
1269
1291
|
|
|
1270
1292
|
await this.chat(prompt);
|
|
1271
1293
|
}
|
|
@@ -1277,66 +1299,19 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1277
1299
|
showCommandMenu() {
|
|
1278
1300
|
const c = colors;
|
|
1279
1301
|
const width = terminalWidth(72, 112, 92);
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
if (!right) return left;
|
|
1285
|
-
return `${padVisible(left, split)} ${padVisible(right, rightWidth)}`;
|
|
1286
|
-
};
|
|
1287
|
-
|
|
1288
|
-
const body = [
|
|
1289
|
-
`${c.bright}${c.cyan}${this.useUnicodeUi ? '? ' : ''}WINTER COMMANDS${c.reset}`,
|
|
1290
|
-
`${c.dim}@file context | @Agent task | !cmd bash | /theme:toggle${c.reset}`,
|
|
1291
|
-
'',
|
|
1292
|
-
`${c.bright}D? ?n & Phi?n l?m vi?c${c.reset}`,
|
|
1293
|
-
row(`${c.yellow}/pwd${c.reset} Th? m?c hi?n t?i`, `${c.yellow}/session${c.reset} Phi?n l?m vi?c`),
|
|
1294
|
-
row(`${c.yellow}/cd${c.reset} ??i th? m?c`, `${c.yellow}/clear${c.reset} X?a m?n h?nh`),
|
|
1295
|
-
row(`${c.yellow}/config${c.reset} Xem c?u h?nh`, `${c.yellow}/exit${c.reset} Tho?t`),
|
|
1296
|
-
'',
|
|
1297
|
-
`${c.bright}AI & C?ng c?${c.reset}`,
|
|
1298
|
-
row(`${c.yellow}/auto${c.reset} TDD t? s?a l?i`, `${c.yellow}/debug${c.reset} Auto debug l?i`),
|
|
1299
|
-
row(`${c.yellow}/doctor${c.reset} Ki?m tra tool-call`, `${c.yellow}/agent${c.reset} Ch?y sub-agent`),
|
|
1300
|
-
row(`${c.yellow}/swe${c.reset} SWE workflow`, `${c.yellow}/plan${c.reset} L?p k? ho?ch`),
|
|
1301
|
-
row(`${c.yellow}/read${c.reset} ??c file`, `${c.yellow}/write${c.reset} Ghi file`),
|
|
1302
|
-
row(`${c.yellow}/bash${c.reset} Ch?y l?nh terminal`, `${c.yellow}/grep${c.reset} T?m trong file`),
|
|
1303
|
-
row(`${c.yellow}/glob${c.reset} T?m file theo pattern`, `${c.yellow}/image${c.reset} ?nh/file/clipboard`),
|
|
1304
|
-
row(`${c.yellow}/paste${c.reset} D?n text/?nh clipboard`, `${c.yellow}/composer${c.reset} Multi-file edit`),
|
|
1305
|
-
row(`${c.yellow}/complete${c.reset} G?i ? code`, `${c.yellow}/search${c.reset} T?m ki?m code`),
|
|
1306
|
-
row(`${c.yellow}/browse${c.reset} M? URL trong tr?nh duy?t`, `${c.yellow}/page-agent${c.reset} GUI Agent resources`),
|
|
1307
|
-
row(`${c.yellow}/ensemble${c.reset} Ch?y nhi?u AI`, `${c.yellow}/vote${c.reset} B?nh ch?n hay nh?t`),
|
|
1308
|
-
row(`${c.yellow}/orchestrate${c.reset} Pipeline ?a model`, `${c.yellow}/undo${c.reset} Undo backup`),
|
|
1309
|
-
row(`${c.yellow}/ecc${c.reset} ECC resource browser`, `${c.yellow}/codex${c.reset} Codex resources`),
|
|
1310
|
-
'',
|
|
1311
|
-
`${c.bright}Git Auto-Pilot${c.reset}`,
|
|
1312
|
-
row(`${c.yellow}/commit${c.reset} AI t? vi?t commit`, `${c.yellow}/review${c.reset} AI review code thay ??i`),
|
|
1313
|
-
'',
|
|
1314
|
-
`${c.bright}C?u h?nh Model${c.reset}`,
|
|
1315
|
-
row(`${c.yellow}/provider${c.reset} ??i provider AI`, `${c.yellow}/model${c.reset} ??i model`),
|
|
1316
|
-
row(`${c.yellow}/providers${c.reset} Danh s?ch provider`, `${c.yellow}/models${c.reset} Danh s?ch model`),
|
|
1317
|
-
row(`${c.yellow}/mcp${c.reset} MCP server mgmt`, `${c.yellow}/permissions${c.reset} Quy?n/allowlist`),
|
|
1318
|
-
'',
|
|
1319
|
-
`${c.bright}B? nh? & K? n?ng${c.reset}`,
|
|
1320
|
-
row(`${c.yellow}/remember${c.reset} L?u v?o b? nh?`, `${c.yellow}/memories${c.reset} Xem b? nh?`),
|
|
1321
|
-
row(`${c.yellow}/skills${c.reset} Danh s?ch k? n?ng`, `${c.yellow}/designs${c.reset} H? th?ng thi?t k?`),
|
|
1322
|
-
];
|
|
1323
|
-
|
|
1324
|
-
console.log(`
|
|
1325
|
-
${renderBox({
|
|
1326
|
-
title: `${c.bright}${c.cyan}WINTER COMMANDS${c.reset}`,
|
|
1302
|
+
const snapshot = buildTuiSnapshot(this);
|
|
1303
|
+
console.log(`\n${renderLandingTui(snapshot, {
|
|
1304
|
+
colors: c,
|
|
1305
|
+
title: 'Winter Agent Console',
|
|
1327
1306
|
width,
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
body,
|
|
1331
|
-
})}
|
|
1332
|
-
${c.dim}G? tin nh?n tr?c ti?p ?? chat ? ESC ?? h?y ? Prompt t? x?p h?ng ch?${c.reset}
|
|
1333
|
-
`);
|
|
1307
|
+
})}`);
|
|
1308
|
+
console.log(`${c.dim}Type ${c.cyan}/${c.dim} for palette, ${c.cyan}/help${c.dim} for the full command list.${c.reset}\n`);
|
|
1334
1309
|
}
|
|
1335
1310
|
|
|
1336
1311
|
showHelp() {
|
|
1337
1312
|
console.log(`
|
|
1338
|
-
${colors.cyan}? WINTER COMMANDS${colors.reset}
|
|
1339
|
-
${colors.dim}${''.padEnd(50,
|
|
1313
|
+
${colors.cyan}${this.useUnicodeUi ? '❄ ' : ''}WINTER COMMANDS${colors.reset}
|
|
1314
|
+
${colors.dim}${''.padEnd(50, this.useUnicodeUi ? '─' : '-')}${colors.reset}
|
|
1340
1315
|
|
|
1341
1316
|
${colors.white}Project:${colors.reset}
|
|
1342
1317
|
/project, /pwd Show current project
|
|
@@ -1417,7 +1392,7 @@ ${colors.reset}
|
|
|
1417
1392
|
}
|
|
1418
1393
|
console.log(`${colors.cyan}Design Systems:${colors.reset}`);
|
|
1419
1394
|
filtered.forEach(e => {
|
|
1420
|
-
const icon = e.isDirectory ? '
|
|
1395
|
+
const icon = e.isDirectory ? '[dir]' : '[file]';
|
|
1421
1396
|
console.log(` ${icon} ${e.name}`);
|
|
1422
1397
|
});
|
|
1423
1398
|
} catch (error) {
|
|
@@ -1437,7 +1412,7 @@ ${colors.reset}
|
|
|
1437
1412
|
const entries = await this.listPathEntries(section.path, 50);
|
|
1438
1413
|
if (entries.length > 0) {
|
|
1439
1414
|
console.log(`${colors.cyan}${section.label}:${colors.reset}`);
|
|
1440
|
-
entries.forEach(e => console.log(` ${e.isDirectory ? '
|
|
1415
|
+
entries.forEach(e => console.log(` ${e.isDirectory ? '[dir]' : '[file]'} ${e.name}`));
|
|
1441
1416
|
}
|
|
1442
1417
|
} catch { }
|
|
1443
1418
|
}
|
|
@@ -1451,8 +1426,8 @@ ${colors.reset}
|
|
|
1451
1426
|
const providers = this.ai.listProviders();
|
|
1452
1427
|
console.log(`${colors.cyan}Configured Models:${colors.reset}`);
|
|
1453
1428
|
providers.forEach(p => {
|
|
1454
|
-
const active = p.name === this.ai.getActiveProvider() ? ` ${colors.green}
|
|
1455
|
-
const status = p.ready ? `${colors.green}
|
|
1429
|
+
const active = p.name === this.ai.getActiveProvider() ? ` ${colors.green}< active${colors.reset}` : '';
|
|
1430
|
+
const status = p.ready ? `${colors.green}ok${colors.reset}` : `${colors.red}off${colors.reset}`;
|
|
1456
1431
|
console.log(` ${status} ${colors.bright}${p.name}${colors.reset}: ${p.model}${active}`);
|
|
1457
1432
|
});
|
|
1458
1433
|
|
|
@@ -1500,13 +1475,42 @@ ${colors.reset}
|
|
|
1500
1475
|
}
|
|
1501
1476
|
|
|
1502
1477
|
getActiveModelTier() {
|
|
1478
|
+
if (typeof this.ai?._modelTier === 'string' && this.ai._modelTier) {
|
|
1479
|
+
return this.ai._modelTier;
|
|
1480
|
+
}
|
|
1503
1481
|
const providerName = this.ai?.getActiveProvider?.();
|
|
1504
1482
|
const model = this.ai?.providers?.[providerName]?.model || '';
|
|
1505
1483
|
return classifyModelTier(model, providerName);
|
|
1506
1484
|
}
|
|
1507
1485
|
|
|
1486
|
+
getBudgetScale(modelTier = this.getActiveModelTier()) {
|
|
1487
|
+
return getModelBudgetMultiplier(modelTier);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
getProjectContextBudget(modelTier = this.getActiveModelTier()) {
|
|
1491
|
+
return Math.round(6000 * this.getBudgetScale(modelTier));
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
getCodebaseContextBudget(modelTier = this.getActiveModelTier()) {
|
|
1495
|
+
return Math.round(4200 * this.getBudgetScale(modelTier));
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
getTokenJuiceInlineBudget(modelTier = this.getActiveModelTier()) {
|
|
1499
|
+
return Math.max(800, Math.round(1400 * this.getBudgetScale(modelTier)));
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
getTokenJuiceForModelTier(modelTier = this.getActiveModelTier()) {
|
|
1503
|
+
const tokenJuice = new TokenJuice({
|
|
1504
|
+
projectPath: this.projectPath,
|
|
1505
|
+
inlineBudgetTokens: this.getTokenJuiceInlineBudget(modelTier),
|
|
1506
|
+
});
|
|
1507
|
+
this.tokenJuice = tokenJuice;
|
|
1508
|
+
return tokenJuice;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1508
1511
|
shouldUseCompactPrompt() {
|
|
1509
|
-
|
|
1512
|
+
const tier = this.getActiveModelTier();
|
|
1513
|
+
return tier === 'tiny' || tier === 'small';
|
|
1510
1514
|
}
|
|
1511
1515
|
|
|
1512
1516
|
selectExecutionProfile(messages = [], options = {}) {
|
|
@@ -1573,9 +1577,9 @@ ${colors.reset}
|
|
|
1573
1577
|
const text = this.getLatestUserText(messages).toLowerCase();
|
|
1574
1578
|
if (!text.trim()) return false;
|
|
1575
1579
|
|
|
1576
|
-
const actionPattern = /\b(fix|repair|bug|debug|implement|create|write|edit|modify|update|delete|remove|refactor|run|test|build|commit|push|publish|install|check|inspect|read|scan|grep|search|change|apply|patch|sua|lam|tao|ghi|doc|xoa|chay|kiem tra|cai|them|doi|review|tim)\b/i;
|
|
1577
|
-
const targetPattern = /\b(file|repo|project|code|src|test|build|git|npm|node|folder|directory|cli|tool|provider|model|config|readme|package\.json|du an|thu muc|tap tin|loi|chuc nang)\b|[A-Za-z]:[\\/]|\.js\b|\.ts\b|\.tsx\b|\.json\b|\.md\b/i;
|
|
1578
|
-
const pureQuestionPattern = /^(what|why|how|when|where|is|are|can|could|should|would|tai sao|vi sao|la gi|co nen|co phai)\b/i;
|
|
1580
|
+
const actionPattern = /\b(fix|repair|bug|debug|implement|create|write|edit|modify|update|delete|remove|refactor|run|test|build|commit|push|publish|install|check|inspect|read|scan|grep|search|change|apply|patch|sua|lam|tao|ghi|doc|xoa|chay|kiem tra|cai|them|doi|review|tim|sửa|làm|tạo|đọc|xóa|xoá|chạy|kiểm tra|cài|thêm|đổi|tìm)\b/i;
|
|
1581
|
+
const targetPattern = /\b(file|repo|project|code|src|test|build|git|npm|node|folder|directory|cli|tool|provider|model|config|readme|package\.json|du an|thu muc|tap tin|loi|chuc nang|dự án|thư mục|tập tin|lỗi|chức năng)\b|[A-Za-z]:[\\/]|\.js\b|\.ts\b|\.tsx\b|\.json\b|\.md\b/i;
|
|
1582
|
+
const pureQuestionPattern = /^(what|why|how|when|where|is|are|can|could|should|would|tai sao|vi sao|la gi|co nen|co phai|tại sao|vì sao|là gì|có nên|có phải)\b/i;
|
|
1579
1583
|
|
|
1580
1584
|
if (pureQuestionPattern.test(text) && !actionPattern.test(text)) return false;
|
|
1581
1585
|
return actionPattern.test(text) && targetPattern.test(text);
|
|
@@ -1585,17 +1589,17 @@ ${colors.reset}
|
|
|
1585
1589
|
if (!usedMutatingTools) return false;
|
|
1586
1590
|
const text = String(originalMessage || '').toLowerCase();
|
|
1587
1591
|
if (!text.trim()) return false;
|
|
1588
|
-
if (/\b(skip tests?|no verify|don't verify|khong test|khong verify|bo qua test)\b/i.test(text)) {
|
|
1592
|
+
if (/\b(skip tests?|no verify|don't verify|khong test|khong verify|bo qua test|không test|không verify|bỏ qua test)\b/i.test(text)) {
|
|
1589
1593
|
return false;
|
|
1590
1594
|
}
|
|
1591
|
-
return /\b(fix|bug|error|test|build|lint|typecheck|compile|refactor|implement|edit|write|change|patch|debug|sua|loi|kiem tra|bien dich|trien khai|lam|doi|viet)\b/i.test(text);
|
|
1595
|
+
return /\b(fix|bug|error|test|build|lint|typecheck|compile|refactor|implement|edit|write|change|patch|debug|sua|loi|kiem tra|bien dich|trien khai|lam|doi|viet|sửa|lỗi|kiểm tra|biên dịch|triển khai|làm|đổi|viết)\b/i.test(text);
|
|
1592
1596
|
}
|
|
1593
1597
|
|
|
1594
1598
|
responseNeedsToolEvidence(content = '') {
|
|
1595
1599
|
const text = String(content || '').toLowerCase();
|
|
1596
1600
|
if (!text.trim()) return false;
|
|
1597
1601
|
|
|
1598
|
-
const clarification = /(?:
|
|
1602
|
+
const clarification = /(?:cần thêm|cho mình|vui lòng|please provide|which file|what file|need more|clarify|không rõ|chưa rõ|file nào|thư mục nào|c?n th?m|cho m?nh|vui l?ng|kh?ng r?|ch?a r?|file n?o|th? m?c n?o)/i;
|
|
1599
1603
|
if (clarification.test(text)) return false;
|
|
1600
1604
|
return true;
|
|
1601
1605
|
}
|
|
@@ -1614,17 +1618,52 @@ ${colors.reset}
|
|
|
1614
1618
|
].join('\n');
|
|
1615
1619
|
}
|
|
1616
1620
|
|
|
1621
|
+
withCurrentAbortSignal(options = {}) {
|
|
1622
|
+
const signal = options.signal || options.abortSignal || this.currentAbortController?.signal;
|
|
1623
|
+
return signal ? { ...options, signal } : options;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
isAbortError(error) {
|
|
1627
|
+
return error?.name === 'AbortError' || error?.message === 'AbortError';
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
isRateLimitError(error) {
|
|
1631
|
+
const message = String(error?.message || error || '');
|
|
1632
|
+
return error?.status === 429 || /\b429\b|rate[_ -]?limit|tokens per minute|\bTPM\b/i.test(message);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
isTimeoutError(error) {
|
|
1636
|
+
const message = String(error?.message || error || '');
|
|
1637
|
+
return error?.name === 'TimeoutError'
|
|
1638
|
+
|| error?.code === 'ETIMEDOUT'
|
|
1639
|
+
|| /timed out|timeout|request aborted/i.test(message);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
cancelCurrentTask() {
|
|
1643
|
+
if (this.isCancelled) return;
|
|
1644
|
+
this.isCancelled = true;
|
|
1645
|
+
if (this.spinner) this.spinner.stop();
|
|
1646
|
+
if (this.currentAbortController && !this.currentAbortController.signal.aborted) {
|
|
1647
|
+
this.currentAbortController.abort(new DOMException('The operation was aborted.', 'AbortError'));
|
|
1648
|
+
}
|
|
1649
|
+
console.log(`\n\x1b[31m[ Đã hủy công việc hiện tại ]\x1b[0m`);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1617
1652
|
async requestAssistantTurn(messages, options, startedAt, totalUsage) {
|
|
1653
|
+
const requestOptions = this.withCurrentAbortSignal(options);
|
|
1618
1654
|
if (typeof this.ai.streamRequest === 'function') {
|
|
1619
1655
|
try {
|
|
1620
|
-
const streamed = await this.collectAssistantStream(messages,
|
|
1656
|
+
const streamed = await this.collectAssistantStream(messages, requestOptions, startedAt, totalUsage);
|
|
1621
1657
|
if (streamed) return streamed;
|
|
1622
1658
|
} catch (error) {
|
|
1659
|
+
if (this.isAbortError(error)) throw new Error('AbortError');
|
|
1660
|
+
if (this.isRateLimitError(error)) throw error;
|
|
1661
|
+
if (this.isTimeoutError(error)) throw error;
|
|
1623
1662
|
console.log(`${colors.dim}Streaming failed, retrying normal response: ${error.message}${colors.reset}`);
|
|
1624
1663
|
}
|
|
1625
1664
|
}
|
|
1626
1665
|
|
|
1627
|
-
const response = await this.ai.sendRequest(messages,
|
|
1666
|
+
const response = await this.ai.sendRequest(messages, requestOptions);
|
|
1628
1667
|
this.addUsage(totalUsage, response.usage);
|
|
1629
1668
|
const assistantMsg = response.choices?.[0]?.message || {};
|
|
1630
1669
|
const inlineToolExtraction = this.extractInlineToolCalls(assistantMsg.content || '');
|
|
@@ -1783,198 +1822,6 @@ ${colors.reset}
|
|
|
1783
1822
|
process.stdout.write(colors.reset);
|
|
1784
1823
|
}
|
|
1785
1824
|
|
|
1786
|
-
installSlashSuggestions() {
|
|
1787
|
-
if (!process.stdin.isTTY) return;
|
|
1788
|
-
|
|
1789
|
-
readline.emitKeypressEvents(process.stdin, this.rl);
|
|
1790
|
-
|
|
1791
|
-
process.stdin.on('keypress', (str, key = {}) => {
|
|
1792
|
-
if (key.ctrl && key.name === 'v') {
|
|
1793
|
-
void this.handleDirectClipboardPaste();
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
if (key.ctrl || key.meta) return;
|
|
1797
|
-
|
|
1798
|
-
if (typeof str === 'string' && str.length > 1) {
|
|
1799
|
-
return;
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
if (this.slashMenu.open && this.handleSlashMenuKey(key)) {
|
|
1803
|
-
return;
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
if (key.name === 'escape' && this.isProcessing) {
|
|
1807
|
-
this.isCancelled = true;
|
|
1808
|
-
if (this.spinner) this.spinner.stop();
|
|
1809
|
-
console.log(`\n\x1b[31m[ ?? nh?n l?nh H?Y... AI s? k?t th?c ? thao t?c ti?p theo ]\x1b[0m`);
|
|
1810
|
-
return;
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
queueMicrotask(() => {
|
|
1814
|
-
const line = this.rl?.line || '';
|
|
1815
|
-
if (!line.startsWith('/')) {
|
|
1816
|
-
this.closeSlashMenu();
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
this.openSlashMenu(line);
|
|
1821
|
-
});
|
|
1822
|
-
});
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
async handleDirectClipboardPaste() {
|
|
1826
|
-
if (this._handlingDirectClipboardPaste || this.readlineClosed || !this.running) return false;
|
|
1827
|
-
this._handlingDirectClipboardPaste = true;
|
|
1828
|
-
try {
|
|
1829
|
-
const image = await this.getClipboardImage();
|
|
1830
|
-
if (!image) return false;
|
|
1831
|
-
|
|
1832
|
-
const prompt = (this.rl?.line || '').trim() || 'Analyze this pasted clipboard image.';
|
|
1833
|
-
this.closeSlashMenu();
|
|
1834
|
-
if (this.rl?.write) {
|
|
1835
|
-
this.rl.write(null, { ctrl: true, name: 'u' });
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
this.inputQueue = this.inputQueue
|
|
1839
|
-
.then(async () => {
|
|
1840
|
-
this.closeInputBox();
|
|
1841
|
-
await this.processPastedImageTask(prompt, image);
|
|
1842
|
-
})
|
|
1843
|
-
.catch((error) => {
|
|
1844
|
-
this.closeInputBox();
|
|
1845
|
-
console.log(`\n${colors.red}? Paste image error: ${error.message}${colors.reset}\n`);
|
|
1846
|
-
if (this.running && !this.readlineClosed) this.showInputPrompt();
|
|
1847
|
-
});
|
|
1848
|
-
return true;
|
|
1849
|
-
} finally {
|
|
1850
|
-
this._handlingDirectClipboardPaste = false;
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
async processPastedImageTask(prompt, image) {
|
|
1855
|
-
this.isProcessing = true;
|
|
1856
|
-
this.isCancelled = false;
|
|
1857
|
-
try {
|
|
1858
|
-
await this.chat(prompt, [image]);
|
|
1859
|
-
} finally {
|
|
1860
|
-
this.isProcessing = false;
|
|
1861
|
-
if (this.taskQueue.length > 0) {
|
|
1862
|
-
const nextTask = this.taskQueue.shift();
|
|
1863
|
-
setTimeout(() => this.processInputTask(nextTask), 0);
|
|
1864
|
-
} else if (!this.readlineClosed) {
|
|
1865
|
-
this.showInputPrompt();
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
openSlashMenu(line) {
|
|
1871
|
-
const matches = this.getSlashSuggestions(line);
|
|
1872
|
-
if (matches.length === 0) {
|
|
1873
|
-
this.closeSlashMenu();
|
|
1874
|
-
return;
|
|
1875
|
-
}
|
|
1876
|
-
if (this.slashMenu.open && this.slashMenu.line === line) return;
|
|
1877
|
-
|
|
1878
|
-
this.slashMenu = { open: true, line, items: matches, selected: 0, printedLines: this.slashMenu?.printedLines || 0 };
|
|
1879
|
-
this.renderSlashMenu();
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
closeSlashMenu() {
|
|
1883
|
-
if (this.slashMenu && this.slashMenu.printedLines) {
|
|
1884
|
-
readline.moveCursor(process.stdout, 0, -this.slashMenu.printedLines);
|
|
1885
|
-
readline.clearScreenDown(process.stdout);
|
|
1886
|
-
}
|
|
1887
|
-
this.slashMenu = { open: false, line: '', items: [], selected: 0, printedLines: 0 };
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
handleSlashMenuKey(key = {}) {
|
|
1891
|
-
if (key.name === 'up') {
|
|
1892
|
-
this.moveSlashSelection(-1);
|
|
1893
|
-
return true;
|
|
1894
|
-
}
|
|
1895
|
-
if (key.name === 'down') {
|
|
1896
|
-
this.moveSlashSelection(1);
|
|
1897
|
-
return true;
|
|
1898
|
-
}
|
|
1899
|
-
if (key.name === 'tab') {
|
|
1900
|
-
this.acceptSlashSelection();
|
|
1901
|
-
return true;
|
|
1902
|
-
}
|
|
1903
|
-
if (key.name === 'escape') {
|
|
1904
|
-
this.closeSlashMenu();
|
|
1905
|
-
this.rl.prompt(true);
|
|
1906
|
-
return true;
|
|
1907
|
-
}
|
|
1908
|
-
return false;
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
moveSlashSelection(delta) {
|
|
1912
|
-
if (!this.slashMenu.items.length) return;
|
|
1913
|
-
const count = this.slashMenu.items.length;
|
|
1914
|
-
this.slashMenu.selected = (this.slashMenu.selected + delta + count) % count;
|
|
1915
|
-
this.renderSlashMenu();
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
acceptSlashSelection() {
|
|
1919
|
-
const item = this.slashMenu.items[this.slashMenu.selected];
|
|
1920
|
-
if (!item) return;
|
|
1921
|
-
|
|
1922
|
-
const currentLine = String(this.rl?.line ?? this.slashMenu.line ?? '');
|
|
1923
|
-
const slashPrefixMatch = currentLine.match(/^\s*(\/\S*)(.*)$/);
|
|
1924
|
-
const prefix = slashPrefixMatch ? slashPrefixMatch[1] : currentLine.trim();
|
|
1925
|
-
const suffixText = slashPrefixMatch ? slashPrefixMatch[2] : '';
|
|
1926
|
-
const needsSpace = item.usage && suffixText && !/^\s/.test(suffixText);
|
|
1927
|
-
const replacement = `${item.cmd}${needsSpace ? ' ' : ''}${suffixText}`.trimEnd();
|
|
1928
|
-
|
|
1929
|
-
this.rl.write(null, { ctrl: true, name: 'u' });
|
|
1930
|
-
this.rl.write(replacement || prefix || item.cmd);
|
|
1931
|
-
this.closeSlashMenu();
|
|
1932
|
-
this.rl.prompt(true);
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
|
-
renderSlashMenu() {
|
|
1936
|
-
const matches = this.slashMenu.items;
|
|
1937
|
-
if (!matches.length) return;
|
|
1938
|
-
|
|
1939
|
-
if (this.slashMenu.printedLines) {
|
|
1940
|
-
readline.moveCursor(process.stdout, 0, -this.slashMenu.printedLines);
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
process.stdout.write('\n');
|
|
1944
|
-
readline.clearLine(process.stdout, 1);
|
|
1945
|
-
process.stdout.write(`${colors.dim}Commands${colors.reset}\n`);
|
|
1946
|
-
|
|
1947
|
-
const maxDisplay = 5;
|
|
1948
|
-
const displayedMatches = matches.slice(0, maxDisplay);
|
|
1949
|
-
|
|
1950
|
-
displayedMatches.forEach((item, index) => {
|
|
1951
|
-
readline.clearLine(process.stdout, 1);
|
|
1952
|
-
const usage = item.usage ? ` ${colors.dim}${item.usage}${colors.reset}` : '';
|
|
1953
|
-
const pointer = index === this.slashMenu.selected ? `${colors.green}>${colors.reset}` : ' ';
|
|
1954
|
-
process.stdout.write(`${pointer} ${colors.cyan}${item.cmd}${colors.reset} ${colors.dim}${item.desc}${colors.reset}${usage}\n`);
|
|
1955
|
-
});
|
|
1956
|
-
|
|
1957
|
-
if (matches.length > maxDisplay) {
|
|
1958
|
-
readline.clearLine(process.stdout, 1);
|
|
1959
|
-
process.stdout.write(` ${colors.dim}... v? ${matches.length - maxDisplay} l?nh kh?c (g? ti?p ?? l?c)${colors.reset}\n`);
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
readline.clearLine(process.stdout, 1);
|
|
1963
|
-
process.stdout.write(`${colors.dim}?/? ch?n ? Enter/Tab d?ng ? Esc ??ng${colors.reset}\n`);
|
|
1964
|
-
|
|
1965
|
-
// X?a c?c d?ng th?a n?u s? l??ng d?ng m?i ?t h?n s? l??ng d?ng c?
|
|
1966
|
-
const currentLines = Math.min(matches.length, maxDisplay) + 3 + (matches.length > maxDisplay ? 1 : 0);
|
|
1967
|
-
if (this.slashMenu.printedLines > currentLines) {
|
|
1968
|
-
for (let i = 0; i < this.slashMenu.printedLines - currentLines; i++) {
|
|
1969
|
-
readline.clearLine(process.stdout, 1);
|
|
1970
|
-
process.stdout.write('\n');
|
|
1971
|
-
}
|
|
1972
|
-
readline.moveCursor(process.stdout, 0, -(this.slashMenu.printedLines - currentLines));
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
this.slashMenu.printedLines = currentLines;
|
|
1976
|
-
this.rl.prompt(true);
|
|
1977
|
-
}
|
|
1978
1825
|
|
|
1979
1826
|
getSlashSuggestions(line) {
|
|
1980
1827
|
const query = String(line || '').trim();
|
|
@@ -1984,7 +1831,7 @@ ${colors.reset}
|
|
|
1984
1831
|
const preferred = [
|
|
1985
1832
|
'/help', '/new', '/history', '/exit', '/pwd', '/cd',
|
|
1986
1833
|
'/provider', '/model', '/models', '/providers',
|
|
1987
|
-
'/theme:toggle',
|
|
1834
|
+
'/theme:toggle', '/tui',
|
|
1988
1835
|
'/auto', '/debug', '/doctor', '/context', '/scorecard', '/swe',
|
|
1989
1836
|
'/read', '/write', '/glob', '/grep', '/bash',
|
|
1990
1837
|
'/codex', '/claude', '/karpathy', '/agents',
|
|
@@ -2106,6 +1953,7 @@ ${colors.reset}
|
|
|
2106
1953
|
provider: executionProfile.provider,
|
|
2107
1954
|
model: executionProfile.model,
|
|
2108
1955
|
enableTools: false,
|
|
1956
|
+
signal: this.currentAbortController?.signal,
|
|
2109
1957
|
});
|
|
2110
1958
|
this.addUsage(totalUsage, response.usage);
|
|
2111
1959
|
const content = response.choices?.[0]?.message?.content || '';
|
|
@@ -2114,6 +1962,7 @@ ${colors.reset}
|
|
|
2114
1962
|
}
|
|
2115
1963
|
return content;
|
|
2116
1964
|
} catch (error) {
|
|
1965
|
+
if (this.isAbortError(error)) throw new Error('AbortError');
|
|
2117
1966
|
const fallback = this.buildToolFallbackAnswer(toolSummaries, error.message);
|
|
2118
1967
|
console.log(`\n${colors.yellow}${fallback}${colors.reset}\n`);
|
|
2119
1968
|
return fallback;
|
|
@@ -2131,6 +1980,7 @@ ${colors.reset}
|
|
|
2131
1980
|
provider: profile.provider,
|
|
2132
1981
|
model: profile.model,
|
|
2133
1982
|
enableTools: false,
|
|
1983
|
+
signal: this.currentAbortController?.signal,
|
|
2134
1984
|
})) {
|
|
2135
1985
|
if (chunk.usage) this.addUsage(totalUsage, chunk.usage);
|
|
2136
1986
|
if (chunk.content) {
|
|
@@ -2146,6 +1996,9 @@ ${colors.reset}
|
|
|
2146
1996
|
}
|
|
2147
1997
|
} catch (error) {
|
|
2148
1998
|
process.stdout.write(colors.reset);
|
|
1999
|
+
if (this.isAbortError(error)) throw new Error('AbortError');
|
|
2000
|
+
if (this.isRateLimitError(error)) throw error;
|
|
2001
|
+
if (this.isTimeoutError(error)) throw error;
|
|
2149
2002
|
console.log(`${colors.dim}Streaming failed, retrying normal response: ${error.message}${colors.reset}`);
|
|
2150
2003
|
}
|
|
2151
2004
|
|
|
@@ -2153,6 +2006,7 @@ ${colors.reset}
|
|
|
2153
2006
|
provider: profile.provider,
|
|
2154
2007
|
model: profile.model,
|
|
2155
2008
|
enableTools: false,
|
|
2009
|
+
signal: this.currentAbortController?.signal,
|
|
2156
2010
|
});
|
|
2157
2011
|
this.addUsage(totalUsage, response.usage);
|
|
2158
2012
|
content = response.choices?.[0]?.message?.content || '';
|
|
@@ -2165,17 +2019,13 @@ ${colors.reset}
|
|
|
2165
2019
|
printAssistantAnswer(content, startedAt, usage = {}) {
|
|
2166
2020
|
const formatted = formatMarkdown(content);
|
|
2167
2021
|
const footer = this.formatAnswerFooter(startedAt, usage);
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2022
|
+
console.log(`\n${renderAssistantPanel({
|
|
2023
|
+
content: formatted,
|
|
2024
|
+
footer,
|
|
2025
|
+
colors,
|
|
2171
2026
|
width: terminalWidth(72, 120, 92),
|
|
2172
|
-
borderColor: colors.blue,
|
|
2173
|
-
titleColor: colors.cyan,
|
|
2174
|
-
body: [...body, '', `${colors.dim}${footer}${colors.reset}`],
|
|
2175
2027
|
})}\n`);
|
|
2176
2028
|
return;
|
|
2177
|
-
console.log(`${colors.dim}${'?'.repeat(50)}${colors.reset}`);
|
|
2178
|
-
console.log(`${colors.dim}${this.formatAnswerFooter(startedAt, usage)}${colors.reset}\n`);
|
|
2179
2029
|
}
|
|
2180
2030
|
|
|
2181
2031
|
formatAnswerFooter(startedAt, usage = {}) {
|
|
@@ -2223,16 +2073,29 @@ ${colors.reset}
|
|
|
2223
2073
|
}
|
|
2224
2074
|
|
|
2225
2075
|
async promptToolPermission(commandText) {
|
|
2226
|
-
const
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2076
|
+
const c = colors;
|
|
2077
|
+
const width = terminalWidth(68, 100, 80);
|
|
2078
|
+
const body = [
|
|
2079
|
+
`${c.yellow}${this.useUnicodeUi ? '⚠' : '!'} AI wants to run${c.reset}`,
|
|
2080
|
+
`${c.bright}${c.white}${commandText}${c.reset}`,
|
|
2081
|
+
'',
|
|
2082
|
+
`${c.cyan}1.${c.reset} Allow once`,
|
|
2083
|
+
`${c.cyan}2.${c.reset} Allow for session`,
|
|
2084
|
+
`${c.cyan}3.${c.reset} Deny`,
|
|
2085
|
+
];
|
|
2086
|
+
|
|
2087
|
+
console.log(renderBox({
|
|
2088
|
+
title: 'Tool Permission',
|
|
2089
|
+
width,
|
|
2090
|
+
borderColor: c.magenta,
|
|
2091
|
+
titleColor: c.yellow,
|
|
2092
|
+
body,
|
|
2093
|
+
boxChars: { topLeft: '+', topRight: '+', bottomLeft: '+', bottomRight: '+', horizontal: '-', vertical: '|', teeLeft: '+', teeRight: '+' },
|
|
2094
|
+
}));
|
|
2232
2095
|
|
|
2233
2096
|
while (true) {
|
|
2234
2097
|
const answer = await new Promise(resolve => {
|
|
2235
|
-
this.rl.question(`${
|
|
2098
|
+
this.rl.question(`${c.yellow}Choice [1/2/3]: ${c.reset}`, resolve);
|
|
2236
2099
|
});
|
|
2237
2100
|
|
|
2238
2101
|
const choice = String(answer || '').trim().toLowerCase();
|
|
@@ -2246,7 +2109,7 @@ ${colors.reset}
|
|
|
2246
2109
|
return false;
|
|
2247
2110
|
}
|
|
2248
2111
|
|
|
2249
|
-
|
|
2112
|
+
console.log(`${c.dim}Please choose 1, 2, or 3.${c.reset}`);
|
|
2250
2113
|
}
|
|
2251
2114
|
}
|
|
2252
2115
|
|
|
@@ -2297,7 +2160,7 @@ ${colors.reset}
|
|
|
2297
2160
|
await this.session.replaceMemory('[Conversation Summary]', compressed.summary, 'summary');
|
|
2298
2161
|
|
|
2299
2162
|
if (verbose) {
|
|
2300
|
-
console.log(`${colors.green}
|
|
2163
|
+
console.log(`${colors.green}✓ Compressed ${compressed.omittedCount} old message(s) into session summary.${colors.reset}`);
|
|
2301
2164
|
}
|
|
2302
2165
|
return compressed;
|
|
2303
2166
|
}
|
|
@@ -2371,7 +2234,7 @@ ${colors.reset}
|
|
|
2371
2234
|
this.rl.question(`${colors.yellow}Apply is not automatic here. Continue? [y/N]: ${colors.reset}`, resolve);
|
|
2372
2235
|
});
|
|
2373
2236
|
console.log(/^y(es)?$/i.test(String(answer || '').trim())
|
|
2374
|
-
? `${colors.green}
|
|
2237
|
+
? `${colors.green}✓ Confirmed${colors.reset}`
|
|
2375
2238
|
: `${colors.dim}Cancelled${colors.reset}`);
|
|
2376
2239
|
}
|
|
2377
2240
|
}
|
|
@@ -2380,7 +2243,7 @@ ${colors.reset}
|
|
|
2380
2243
|
const action = args[0];
|
|
2381
2244
|
if (action === 'stop') {
|
|
2382
2245
|
this.stopWatchers();
|
|
2383
|
-
console.log(`${colors.green}
|
|
2246
|
+
console.log(`${colors.green}✓ Watcher stopped${colors.reset}`);
|
|
2384
2247
|
return;
|
|
2385
2248
|
}
|
|
2386
2249
|
|
|
@@ -2405,7 +2268,7 @@ ${colors.reset}
|
|
|
2405
2268
|
run(name);
|
|
2406
2269
|
});
|
|
2407
2270
|
this.watchers.push(watcher);
|
|
2408
|
-
console.log(`${colors.green}
|
|
2271
|
+
console.log(`${colors.green}✓ Watching ${this.projectPath}${colors.reset}`);
|
|
2409
2272
|
console.log(`${colors.dim}Command: ${command}. Use /watch stop to stop.${colors.reset}`);
|
|
2410
2273
|
}
|
|
2411
2274
|
|
|
@@ -2438,7 +2301,7 @@ ${colors.reset}
|
|
|
2438
2301
|
const promptHistory = this.getCompressedPromptHistory({
|
|
2439
2302
|
limit: 20,
|
|
2440
2303
|
keepRecent: 14,
|
|
2441
|
-
maxTotalChars:
|
|
2304
|
+
maxTotalChars: 16000,
|
|
2442
2305
|
});
|
|
2443
2306
|
if (promptHistory.summary) {
|
|
2444
2307
|
messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
|
|
@@ -2468,7 +2331,7 @@ ${colors.reset}
|
|
|
2468
2331
|
await this.session.addToHistory({ role: 'user', content: message });
|
|
2469
2332
|
await this.session.addToHistory({ role: 'assistant', content: finalContent });
|
|
2470
2333
|
|
|
2471
|
-
//
|
|
2334
|
+
// Tự động verify: nếu AI đã dùng tools (sửa code), chạy test/build.
|
|
2472
2335
|
if (finalContent && this.shouldAutoVerifyAfterTools(message, usedMutatingTools)) {
|
|
2473
2336
|
const sessionContext = this.session?.getContext?.() || {};
|
|
2474
2337
|
const profile = String(sessionContext.workflowProfile || 'general');
|
|
@@ -2479,7 +2342,8 @@ ${colors.reset}
|
|
|
2479
2342
|
}
|
|
2480
2343
|
|
|
2481
2344
|
} catch (error) {
|
|
2482
|
-
|
|
2345
|
+
if (this.isAbortError(error)) throw error;
|
|
2346
|
+
console.log(`\n${colors.red}✖ Error: ${error.message}${colors.reset}\n`);
|
|
2483
2347
|
}
|
|
2484
2348
|
}
|
|
2485
2349
|
|
|
@@ -2541,7 +2405,7 @@ ${colors.reset}
|
|
|
2541
2405
|
}
|
|
2542
2406
|
|
|
2543
2407
|
/**
|
|
2544
|
-
*
|
|
2408
|
+
* Chạy verification commands (test, build) và trả về kết quả.
|
|
2545
2409
|
*/
|
|
2546
2410
|
async inferVerificationCommands(task = '') {
|
|
2547
2411
|
const fs = await import('fs/promises');
|
|
@@ -2551,7 +2415,7 @@ ${colors.reset}
|
|
|
2551
2415
|
const pkg = JSON.parse(await fs.readFile(packagePath, 'utf8'));
|
|
2552
2416
|
const scripts = pkg.scripts || {};
|
|
2553
2417
|
if (scripts.test) candidates.push('npm test');
|
|
2554
|
-
if (scripts.build && /\b(build|compile|type|typescript|tsc|frontend|ui|design|next|vite|react|debug|fix|bug|error|
|
|
2418
|
+
if (scripts.build && /\b(build|compile|type|typescript|tsc|frontend|ui|design|next|vite|react|debug|fix|bug|error|loi|lỗi)\b/i.test(task)) {
|
|
2555
2419
|
candidates.push('npm run build');
|
|
2556
2420
|
}
|
|
2557
2421
|
if (scripts.lint && /\b(lint|style|eslint|quality|review)\b/i.test(task)) candidates.push('npm run lint');
|
|
@@ -2593,10 +2457,10 @@ ${colors.reset}
|
|
|
2593
2457
|
}
|
|
2594
2458
|
|
|
2595
2459
|
/**
|
|
2596
|
-
*
|
|
2597
|
-
* -
|
|
2598
|
-
* -
|
|
2599
|
-
* -
|
|
2460
|
+
* Vòng lặp tự động verify + sửa lỗi:
|
|
2461
|
+
* - Chạy test/build
|
|
2462
|
+
* - Nếu fail, gửi lại cho AI fix
|
|
2463
|
+
* - Lặp đến khi pass hết hoặc hết số lần thử
|
|
2600
2464
|
*/
|
|
2601
2465
|
async verifyAndHeal(messages, tools, maxAttempts = 5) {
|
|
2602
2466
|
const verifCommands = await this.inferVerificationCommands(this.getLatestUserText(messages));
|
|
@@ -2607,7 +2471,7 @@ ${colors.reset}
|
|
|
2607
2471
|
const result = await this.runVerification(verifCommands);
|
|
2608
2472
|
|
|
2609
2473
|
if (result.passed) {
|
|
2610
|
-
console.log(`\n${colors.green}
|
|
2474
|
+
console.log(`\n${colors.green}✓ All verifications passed!${colors.reset}\n`);
|
|
2611
2475
|
return;
|
|
2612
2476
|
}
|
|
2613
2477
|
|
|
@@ -2617,7 +2481,7 @@ ${colors.reset}
|
|
|
2617
2481
|
.map(r => `Command: ${r.cmd}\n${r.output}`)
|
|
2618
2482
|
.join('\n\n---\n\n');
|
|
2619
2483
|
|
|
2620
|
-
console.log(`\n${colors.yellow}
|
|
2484
|
+
console.log(`\n${colors.yellow}Verification failed. Sending errors back to AI for fix...${colors.reset}\n`);
|
|
2621
2485
|
|
|
2622
2486
|
// Push error output as user message for AI to fix
|
|
2623
2487
|
const fixPrompt = `VERIFICATION FAILED (attempt ${attempt}/${maxAttempts}):
|
|
@@ -2635,12 +2499,12 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2635
2499
|
const { usedTools: fixUsedTools } = await this.runConversation(messages, 'Fixing', tools);
|
|
2636
2500
|
|
|
2637
2501
|
if (!fixUsedTools) {
|
|
2638
|
-
console.log(`\n${colors.red}
|
|
2502
|
+
console.log(`\n${colors.red}AI did not attempt to fix the errors. Stopping.${colors.reset}\n`);
|
|
2639
2503
|
break;
|
|
2640
2504
|
}
|
|
2641
2505
|
}
|
|
2642
2506
|
|
|
2643
|
-
console.log(`\n${colors.red}
|
|
2507
|
+
console.log(`\n${colors.red}Max verification attempts (${maxAttempts}) reached. Some issues may remain.${colors.reset}\n`);
|
|
2644
2508
|
}
|
|
2645
2509
|
|
|
2646
2510
|
shouldUseTools(message = '', imageAttachments = []) {
|
|
@@ -2676,9 +2540,9 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2676
2540
|
];
|
|
2677
2541
|
|
|
2678
2542
|
const promptHistory = this.getCompressedPromptHistory({
|
|
2679
|
-
limit:
|
|
2680
|
-
keepRecent:
|
|
2681
|
-
maxTotalChars:
|
|
2543
|
+
limit: 40,
|
|
2544
|
+
keepRecent: 16,
|
|
2545
|
+
maxTotalChars: 16000,
|
|
2682
2546
|
});
|
|
2683
2547
|
if (promptHistory.summary) {
|
|
2684
2548
|
messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
|
|
@@ -2770,14 +2634,14 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2770
2634
|
const passed = result.usedTools && Boolean(readEvent || /readme\.md/i.test(result.finalContent || ''));
|
|
2771
2635
|
|
|
2772
2636
|
if (passed) {
|
|
2773
|
-
console.log(`${colors.green}
|
|
2637
|
+
console.log(`${colors.green}✓ Tool calling works for ${provider}/${model}.${colors.reset}`);
|
|
2774
2638
|
if (readEvent) {
|
|
2775
2639
|
console.log(`${colors.dim} Last Read result: ${readEvent.result?.path || probePath}${colors.reset}`);
|
|
2776
2640
|
}
|
|
2777
2641
|
return { success: true, provider, model, usedTools: result.usedTools, beforeEvents };
|
|
2778
2642
|
}
|
|
2779
2643
|
|
|
2780
|
-
console.log(`${colors.red}
|
|
2644
|
+
console.log(`${colors.red}✖ Tool calling did not execute for ${provider}/${model}.${colors.reset}`);
|
|
2781
2645
|
console.log(`${colors.yellow} Try a stronger model or use a provider that supports OpenAI-compatible tools/fallback text output.${colors.reset}`);
|
|
2782
2646
|
return { success: false, provider, model, usedTools: result.usedTools, beforeEvents };
|
|
2783
2647
|
}
|
|
@@ -2858,17 +2722,24 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2858
2722
|
}
|
|
2859
2723
|
|
|
2860
2724
|
async getProjectContext(task = '') {
|
|
2725
|
+
const modelTier = this.getActiveModelTier();
|
|
2861
2726
|
const context = [];
|
|
2862
2727
|
const requiredLocalResources = await this.getRequiredLocalResourceSummary();
|
|
2863
2728
|
if (requiredLocalResources) {
|
|
2864
2729
|
context.push(requiredLocalResources);
|
|
2865
2730
|
}
|
|
2866
2731
|
|
|
2732
|
+
const shouldIncludeResources = /\b(resource|resources|skill|skills|plugin|plugins|claude|codex|agent|agents|design|ui|figma|brand|mcp)\b/i.test(String(task || ''));
|
|
2733
|
+
const localResources = shouldIncludeResources ? await this.getLocalResourceContext() : '';
|
|
2734
|
+
if (localResources) {
|
|
2735
|
+
context.push(localResources);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2867
2738
|
const projectInstructionFiles = await this.readProjectInstructionFiles();
|
|
2868
2739
|
|
|
2869
2740
|
for (const file of projectInstructionFiles) {
|
|
2870
2741
|
try {
|
|
2871
|
-
const preview = this.compactText(file.content,
|
|
2742
|
+
const preview = this.compactText(file.content, 1200, 'project instruction');
|
|
2872
2743
|
context.push(`[${file.relativePath}]\n${preview}`);
|
|
2873
2744
|
} catch { }
|
|
2874
2745
|
}
|
|
@@ -2878,21 +2749,24 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2878
2749
|
const stat = await fs.stat(packageJsonPath);
|
|
2879
2750
|
if (stat.isFile()) {
|
|
2880
2751
|
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
2881
|
-
context.push(`[package.json]\n${this.compactText(content,
|
|
2752
|
+
context.push(`[package.json]\n${this.compactText(content, 1600, 'package.json')}`);
|
|
2882
2753
|
}
|
|
2883
2754
|
} catch { }
|
|
2884
2755
|
|
|
2885
|
-
const shouldIncludeResources = /\b(resource|resources|skill|skills|plugin|plugins|claude|codex|agent|agents|design|ui|figma|brand|mcp)\b/i.test(String(task || ''));
|
|
2886
|
-
const localResources = shouldIncludeResources ? await this.getLocalResourceContext() : '';
|
|
2887
|
-
if (localResources) {
|
|
2888
|
-
context.push(localResources);
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
2756
|
const codebaseContext = await this.buildCodebaseContext(task);
|
|
2892
2757
|
if (codebaseContext) {
|
|
2893
2758
|
context.push(codebaseContext);
|
|
2894
2759
|
}
|
|
2895
2760
|
|
|
2761
|
+
const graphContext = await this.codebaseSearcher?.buildGraphContext?.(task, {
|
|
2762
|
+
maxNodes: 24,
|
|
2763
|
+
maxCodeBlocks: 8,
|
|
2764
|
+
maxCodeBlockSize: 1800,
|
|
2765
|
+
});
|
|
2766
|
+
if (graphContext) {
|
|
2767
|
+
context.push(`[CodeGraph Context]\n${this.compactText(graphContext, 5200, 'codegraph context')}`);
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2896
2770
|
// Git Context
|
|
2897
2771
|
try {
|
|
2898
2772
|
const { execSync } = await import('child_process');
|
|
@@ -2902,20 +2776,20 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2902
2776
|
|
|
2903
2777
|
const gitSummary = execSync('git diff --stat --summary', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim();
|
|
2904
2778
|
if (gitSummary) {
|
|
2905
|
-
context.push(`[Git Summary]\n${this.compactText(gitSummary,
|
|
2779
|
+
context.push(`[Git Summary]\n${this.compactText(gitSummary, 1200, 'git summary')}`);
|
|
2906
2780
|
}
|
|
2907
2781
|
|
|
2908
2782
|
// Get brief git diff for context
|
|
2909
2783
|
const gitDiff = execSync('git diff', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim().split('\n').slice(0, 30).join('\n');
|
|
2910
2784
|
if (gitDiff) {
|
|
2911
|
-
context.push(`[Git Diff]\n${this.compactText(gitDiff,
|
|
2785
|
+
context.push(`[Git Diff]\n${this.compactText(gitDiff, 2200, 'git diff')}`);
|
|
2912
2786
|
}
|
|
2913
2787
|
}
|
|
2914
2788
|
} catch (e) {
|
|
2915
2789
|
// Not a git repo or git not installed
|
|
2916
2790
|
}
|
|
2917
2791
|
|
|
2918
|
-
return this.compactText(context.join('\n\n') || 'No project context found.', this.
|
|
2792
|
+
return this.compactText(context.join('\n\n') || 'No project context found.', this.getProjectContextBudget(modelTier), 'project context');
|
|
2919
2793
|
}
|
|
2920
2794
|
|
|
2921
2795
|
async getLocalResourceContext() {
|
|
@@ -3017,7 +2891,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3017
2891
|
this.promptBuilder.tools = this.tools;
|
|
3018
2892
|
this.promptBuilder.sessionPermissionGrants = this.sessionPermissionGrants;
|
|
3019
2893
|
return this.promptBuilder.buildSystemPrompt(context, {
|
|
3020
|
-
|
|
2894
|
+
modelTier: this.getActiveModelTier(),
|
|
3021
2895
|
});
|
|
3022
2896
|
}
|
|
3023
2897
|
|
|
@@ -3064,7 +2938,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3064
2938
|
}
|
|
3065
2939
|
config.mcp.servers.forEach(server => {
|
|
3066
2940
|
const enabled = server.enabled === false ? `${colors.red}disabled${colors.reset}` : `${colors.green}enabled${colors.reset}`;
|
|
3067
|
-
console.log(`
|
|
2941
|
+
console.log(` - ${server.name} (${enabled}) -> ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''}`);
|
|
3068
2942
|
});
|
|
3069
2943
|
break;
|
|
3070
2944
|
case 'add': {
|
|
@@ -3086,7 +2960,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3086
2960
|
config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
|
|
3087
2961
|
config.mcp.servers.push({ name, command, args: parsedArgs, enabled: true });
|
|
3088
2962
|
await this.config.save(config);
|
|
3089
|
-
console.log(`${colors.green}
|
|
2963
|
+
console.log(`${colors.green}✓ Added MCP server: ${name}${colors.reset}`);
|
|
3090
2964
|
break;
|
|
3091
2965
|
}
|
|
3092
2966
|
case 'remove': {
|
|
@@ -3097,7 +2971,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3097
2971
|
}
|
|
3098
2972
|
config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
|
|
3099
2973
|
await this.config.save(config);
|
|
3100
|
-
console.log(`${colors.green}
|
|
2974
|
+
console.log(`${colors.green}✓ Removed MCP server: ${name}${colors.reset}`);
|
|
3101
2975
|
break;
|
|
3102
2976
|
}
|
|
3103
2977
|
case 'allow': {
|
|
@@ -3107,7 +2981,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3107
2981
|
break;
|
|
3108
2982
|
}
|
|
3109
2983
|
await this.config.setPermissionAllowlist({ mcpServers: [name] });
|
|
3110
|
-
console.log(`${colors.green}
|
|
2984
|
+
console.log(`${colors.green}✓ MCP server allowed: ${name}${colors.reset}`);
|
|
3111
2985
|
break;
|
|
3112
2986
|
}
|
|
3113
2987
|
default:
|
|
@@ -3142,13 +3016,13 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3142
3016
|
break;
|
|
3143
3017
|
}
|
|
3144
3018
|
await this.config.setPermissionAllowlist({ [field]: [value] });
|
|
3145
|
-
console.log(`${colors.green}
|
|
3019
|
+
console.log(`${colors.green}✓ Allowed ${kind}: ${value}${colors.reset}`);
|
|
3146
3020
|
break;
|
|
3147
3021
|
}
|
|
3148
3022
|
case 'prompt': {
|
|
3149
3023
|
const value = String(rest[0] || '').toLowerCase();
|
|
3150
3024
|
await this.config.setPermissionAllowlist({ promptByDefault: !(value === 'off' || value === 'false' || value === '0' || value === 'no') });
|
|
3151
|
-
console.log(`${colors.green}
|
|
3025
|
+
console.log(`${colors.green}✓ Updated prompt policy${colors.reset}`);
|
|
3152
3026
|
break;
|
|
3153
3027
|
}
|
|
3154
3028
|
default:
|