4094 lines
156 KiB
Plaintext
4094 lines
156 KiB
Plaintext
• Edited src/ui/spinner.js (+14 -0)
|
||
16 this.leadingWritten = false;
|
||
17 + this.thinkingLineReady = false;
|
||
18 }
|
||
⋮
|
||
26 this.active = true;
|
||
27 + this.thinkingLineReady = false;
|
||
28 if (this.leadingLines > 0 && !this.leadingWritten) {
|
||
⋮
|
||
35
|
||
36 + ensureThinkingLine() {
|
||
37 + if (this.thinkingLineReady) return;
|
||
38 + process.stdout.write('\n');
|
||
39 + this.thinkingLineReady = true;
|
||
40 + readline.moveCursor(process.stdout, 0, -1);
|
||
41 + readline.cursorTo(process.stdout, 0);
|
||
42 + }
|
||
43 +
|
||
44 render() {
|
||
⋮
|
||
64 if (hasThinkingLine) {
|
||
65 + this.ensureThinkingLine();
|
||
66 const width = process.stdout.columns || 80;
|
||
⋮
|
||
102 if (hasThinkingLine) {
|
||
103 + this.ensureThinkingLine();
|
||
104 const width = process.stdout.columns || 80;
|
||
⋮
|
||
126 }
|
||
127 + this.thinkingLineReady = false;
|
||
128 }
|
||
⋮
|
||
133 this.active = false;
|
||
134 + this.thinkingLineReady = false;
|
||
135 readline.clearLine(process.stdout, 0);
|
||
|
||
• Edited src/tools/edit_file.js (+7 -0)
|
||
47 try {
|
||
48 + if (!fs.existsSync(target)) {
|
||
49 + return { success: false, error: '文件不存在' };
|
||
50 + }
|
||
51 + const stat = fs.statSync(target);
|
||
52 + if (!stat.isFile()) {
|
||
53 + return { success: false, error: '目标不是文件' };
|
||
54 + }
|
||
55 const original = fs.readFileSync(target, 'utf8');
|
||
|
||
• Edited src/ui/tool_display.js (+33 -2)
|
||
21
|
||
22 +function splitCommandLines(command) {
|
||
23 + const text = String(command ?? '');
|
||
24 + const parts = text.split(/\r?\n/);
|
||
25 + if (parts.length > 1 && parts[parts.length - 1] === '') parts.pop();
|
||
26 + return parts.length ? parts : [''];
|
||
27 +}
|
||
28 +
|
||
29 +function buildRunCommandLines(label, command) {
|
||
30 + const parts = splitCommandLines(command);
|
||
31 + const firstLine = parts[0] ?? '';
|
||
32 + const startLine = `${label} ${firstLine}`;
|
||
33 + if (parts.length <= 1) {
|
||
34 + return { startLine, finalLine: startLine };
|
||
35 + }
|
||
36 + const preview = parts.slice(1, 3);
|
||
37 + const lines = [startLine, ...preview.map((line) => ` ${line}`), ` 总指令${parts.length}行`];
|
||
38 + return { startLine, finalLine: lines.join('\n') };
|
||
39 +}
|
||
40 +
|
||
41 function buildStartLine(name, args) {
|
||
⋮
|
||
43 if (name === 'run_command') {
|
||
25 - return `${label} ${args.command} (timeout=${args.timeout}s)`;
|
||
44 + const command = args && Object.prototype.hasOwnProperty.call(args, 'command') ? args.command : '';
|
||
45 + const { startLine } = buildRunCommandLines(label, command);
|
||
46 + return `${startLine} (timeout=${args.timeout}s)`;
|
||
47 }
|
||
⋮
|
||
70
|
||
71 +function buildFinalLine(name, args) {
|
||
72 + if (name === 'run_command') {
|
||
73 + const label = blue(toolNameMap(name));
|
||
74 + const command = args && Object.prototype.hasOwnProperty.call(args, 'command') ? args.command : '';
|
||
75 + const { finalLine } = buildRunCommandLines(label, command);
|
||
76 + return `${finalLine} (timeout=${args.timeout}s)`;
|
||
77 + }
|
||
78 + return buildStartLine(name, args);
|
||
79 +}
|
||
80 +
|
||
81 function startToolDisplay(line) {
|
||
⋮
|
||
112 const tail = lines.slice(-5);
|
||
82 - const summary = '运行完成';
|
||
113 + const summary = '运行结果';
|
||
114 return [summary, ...tail];
|
||
|
||
• Edited src/ui/tool_display.js (+6 -6)
|
||
28
|
||
29 -function buildRunCommandLines(label, command) {
|
||
29 +function buildRunCommandLines(label, command, suffix = '') {
|
||
30 const parts = splitCommandLines(command);
|
||
31 const firstLine = parts[0] ?? '';
|
||
32 - const startLine = `${label} ${firstLine}`;
|
||
32 + const startLine = `${label} ${firstLine}${suffix}`;
|
||
33 if (parts.length <= 1) {
|
||
⋮
|
||
44 const command = args && Object.prototype.hasOwnProperty.call(args, 'command') ? args.command : '';
|
||
45 - const { startLine } = buildRunCommandLines(label, command);
|
||
46 - return `${startLine} (timeout=${args.timeout}s)`;
|
||
45 + const { startLine } = buildRunCommandLines(label, command, ` (timeout=${args.timeout}s)`);
|
||
46 + return startLine;
|
||
47 }
|
||
⋮
|
||
74 const command = args && Object.prototype.hasOwnProperty.call(args, 'command') ? args.command : '';
|
||
75 - const { finalLine } = buildRunCommandLines(label, command);
|
||
76 - return `${finalLine} (timeout=${args.timeout}s)`;
|
||
75 + const { finalLine } = buildRunCommandLines(label, command, ` (timeout=${args.timeout}s)`);
|
||
76 + return finalLine;
|
||
77 }
|
||
|
||
• Edited src/ui/tool_display.js (+1 -1)
|
||
194
|
||
195 -module.exports = { buildStartLine, startToolDisplay, formatResultLines, printResultLines };
|
||
195 +module.exports = { buildStartLine, buildFinalLine, startToolDisplay, formatResultLines, printResultLines };
|
||
|
||
• Edited src/cli/index.js (+5 -4)
|
||
16 const { Spinner, truncateThinking } = require('../ui/spinner');
|
||
17 -const { buildStartLine, startToolDisplay, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
17 +const { buildStartLine, buildFinalLine, startToolDisplay, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
18 const { createConversation, updateConversation } = require('../storage/conversation_store');
|
||
⋮
|
||
341 try { args = JSON.parse(call.function.arguments || '{}'); } catch (_) {}
|
||
342 - const line = buildStartLine(call.function.name, args);
|
||
343 - const indicator = startToolDisplay(line);
|
||
342 + const startLine = buildStartLine(call.function.name, args);
|
||
343 + const finalLine = buildFinalLine(call.function.name, args);
|
||
344 + const indicator = startToolDisplay(startLine);
|
||
345 const toolResult = await executeTool({ workspace: WORKSPACE, config, allowMode: state.allowMode, toolCall: call });
|
||
345 - indicator.stop(line);
|
||
346 + indicator.stop(finalLine);
|
||
347 const resultLines = formatResultLines(call.function.name, args, toolResult.raw || { success: toolResult.success, error: t
|
||
oolResult.error });
|
||
|
||
• Edited src/cli/commands.js (+2 -2)
|
||
7 const { runResumeMenu } = require('../ui/resume_menu');
|
||
8 -const { buildStartLine, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
8 +const { buildStartLine, buildFinalLine, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
9 const { createIndentedWriter } = require('../ui/indented_writer');
|
||
⋮
|
||
275 }
|
||
276 - const line = buildStartLine(name, args);
|
||
276 + const line = buildFinalLine(name, args);
|
||
277 // 静态回放:直接输出完成态
|
||
|
||
• Edited src/cli/commands.js (+1 -1)
|
||
7 const { runResumeMenu } = require('../ui/resume_menu');
|
||
8 -const { buildStartLine, buildFinalLine, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
8 +const { buildFinalLine, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
9 const { createIndentedWriter } = require('../ui/indented_writer');
|
||
|
||
• Edited src/ui/tool_display.js (+56 -4)
|
||
7 const DOT_OFF = '◦';
|
||
8 +const ANSI_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
||
9
|
||
⋮
|
||
22
|
||
23 +function stripAnsi(text) {
|
||
24 + return String(text ?? '').replace(ANSI_REGEX, '');
|
||
25 +}
|
||
26 +
|
||
27 +function truncatePlain(text, maxCols) {
|
||
28 + const str = String(text ?? '');
|
||
29 + if (maxCols <= 0) return '';
|
||
30 + if (str.length <= maxCols) return str;
|
||
31 + if (maxCols <= 3) return str.slice(0, maxCols);
|
||
32 + return `${str.slice(0, maxCols - 3)}...`;
|
||
33 +}
|
||
34 +
|
||
35 +function truncateVisible(text, maxCols) {
|
||
36 + const str = String(text ?? '');
|
||
37 + if (maxCols <= 0) return '';
|
||
38 + if (stripAnsi(str).length <= maxCols) return str;
|
||
39 + const useEllipsis = maxCols > 3;
|
||
40 + const limit = maxCols - (useEllipsis ? 3 : 0);
|
||
41 + let out = '';
|
||
42 + let visible = 0;
|
||
43 + const ansiRegex = new RegExp(ANSI_REGEX.source, 'y');
|
||
44 + for (let i = 0; i < str.length && visible < limit; ) {
|
||
45 + ansiRegex.lastIndex = i;
|
||
46 + const match = ansiRegex.exec(str);
|
||
47 + if (match && match.index === i) {
|
||
48 + out += match[0];
|
||
49 + i += match[0].length;
|
||
50 + continue;
|
||
51 + }
|
||
52 + out += str[i];
|
||
53 + visible += 1;
|
||
54 + i += 1;
|
||
55 + }
|
||
56 + if (useEllipsis) out += '...';
|
||
57 + if (str.includes('\x1b[')) out += '\x1b[0m';
|
||
58 + return out;
|
||
59 +}
|
||
60 +
|
||
61 +function normalizePreviewLine(line) {
|
||
62 + return String(line ?? '').replace(/^\s+/, '');
|
||
63 +}
|
||
64 +
|
||
65 function splitCommandLines(command) {
|
||
⋮
|
||
72 function buildRunCommandLines(label, command, suffix = '') {
|
||
73 + const width = Number(process.stdout.columns) || 80;
|
||
74 const parts = splitCommandLines(command);
|
||
75 const firstLine = parts[0] ?? '';
|
||
32 - const startLine = `${label} ${firstLine}${suffix}`;
|
||
76 + const labelLen = stripAnsi(label).length;
|
||
77 + const suffixLen = stripAnsi(suffix).length;
|
||
78 + const available = width - labelLen - 1 - suffixLen;
|
||
79 + const commandLine = available > 0 ? truncatePlain(firstLine, available) : '';
|
||
80 + const startLine = truncateVisible(`${label} ${commandLine}${suffix}`, width);
|
||
81 if (parts.length <= 1) {
|
||
⋮
|
||
83 }
|
||
36 - const preview = parts.slice(1, 3);
|
||
37 - const lines = [startLine, ...preview.map((line) => ` ${line}`), ` 总指令${parts.length}行`];
|
||
84 + const preview = parts.slice(1, 3).map(normalizePreviewLine);
|
||
85 + const lines = [startLine, ...preview.map((line) => ` ${truncatePlain(line, Math.max(0, width - 2))}`), ` 总指令
|
||
${parts.length}行`
|
||
];
|
||
86 return { startLine, finalLine: lines.join('\n') };
|
||
⋮
|
||
130 let on = false;
|
||
131 + const rawLine = String(line ?? '').replace(/\r?\n/g, ' ');
|
||
132 function render() {
|
||
⋮
|
||
135 const dot = on ? DOT_ON : DOT_OFF;
|
||
87 - process.stdout.write(`${dot} ${line}`);
|
||
136 + const width = Number(process.stdout.columns) || 80;
|
||
137 + const maxLine = Math.max(0, width - 2);
|
||
138 + const displayLine = truncateVisible(rawLine, maxLine);
|
||
139 + process.stdout.write(`${dot} ${displayLine}`);
|
||
140 }
|
||
|
||
• Edited src/ui/tool_display.js (+17 -6)
|
||
131 const rawLine = String(line ?? '').replace(/\r?\n/g, ' ');
|
||
132 + let lastLines = 1;
|
||
133 + function clearPrevious() {
|
||
134 + if (lastLines > 1) readline.moveCursor(process.stdout, 0, -(lastLines - 1));
|
||
135 + for (let i = 0; i < lastLines; i += 1) {
|
||
136 + readline.clearLine(process.stdout, 0);
|
||
137 + if (i < lastLines - 1) readline.moveCursor(process.stdout, 0, 1);
|
||
138 + }
|
||
139 + if (lastLines > 1) readline.moveCursor(process.stdout, 0, -(lastLines - 1));
|
||
140 + readline.cursorTo(process.stdout, 0);
|
||
141 + }
|
||
142 function render() {
|
||
133 - readline.clearLine(process.stdout, 0);
|
||
134 - readline.cursorTo(process.stdout, 0);
|
||
143 + clearPrevious();
|
||
144 const dot = on ? DOT_ON : DOT_OFF;
|
||
145 const width = Number(process.stdout.columns) || 80;
|
||
137 - const maxLine = Math.max(0, width - 2);
|
||
146 + const maxLine = Math.max(0, width - 3);
|
||
147 const displayLine = truncateVisible(rawLine, maxLine);
|
||
139 - process.stdout.write(`${dot} ${displayLine}`);
|
||
148 + const lineText = `${dot} ${displayLine}`;
|
||
149 + process.stdout.write(lineText);
|
||
150 + const lineLen = stripAnsi(lineText).length;
|
||
151 + lastLines = Math.max(1, Math.ceil(lineLen / Math.max(1, width)));
|
||
152 }
|
||
⋮
|
||
160 clearInterval(timer);
|
||
149 - readline.clearLine(process.stdout, 0);
|
||
150 - readline.cursorTo(process.stdout, 0);
|
||
161 + clearPrevious();
|
||
162 process.stdout.write(`${DOT_ON} ${finalLine}\n`);
|
||
|
||
• Edited src/ui/tool_display.js (+384 -12)
|
||
8 const ANSI_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
||
9 +const COMBINING_RANGES = [
|
||
10 + [0x0300, 0x036f],
|
||
11 + [0x0483, 0x0489],
|
||
12 + [0x0591, 0x05bd],
|
||
13 + [0x05bf, 0x05bf],
|
||
14 + [0x05c1, 0x05c2],
|
||
15 + [0x05c4, 0x05c5],
|
||
16 + [0x05c7, 0x05c7],
|
||
17 + [0x0610, 0x061a],
|
||
18 + [0x064b, 0x065f],
|
||
19 + [0x0670, 0x0670],
|
||
20 + [0x06d6, 0x06dc],
|
||
21 + [0x06df, 0x06e4],
|
||
22 + [0x06e7, 0x06e8],
|
||
23 + [0x06ea, 0x06ed],
|
||
24 + [0x0711, 0x0711],
|
||
25 + [0x0730, 0x074a],
|
||
26 + [0x07a6, 0x07b0],
|
||
27 + [0x07eb, 0x07f3],
|
||
28 + [0x0816, 0x0819],
|
||
29 + [0x081b, 0x0823],
|
||
30 + [0x0825, 0x0827],
|
||
31 + [0x0829, 0x082d],
|
||
32 + [0x0859, 0x085b],
|
||
33 + [0x0900, 0x0902],
|
||
34 + [0x093a, 0x093a],
|
||
35 + [0x093c, 0x093c],
|
||
36 + [0x0941, 0x0948],
|
||
37 + [0x094d, 0x094d],
|
||
38 + [0x0951, 0x0957],
|
||
39 + [0x0962, 0x0963],
|
||
40 + [0x0981, 0x0981],
|
||
41 + [0x09bc, 0x09bc],
|
||
42 + [0x09c1, 0x09c4],
|
||
43 + [0x09cd, 0x09cd],
|
||
44 + [0x09e2, 0x09e3],
|
||
45 + [0x0a01, 0x0a02],
|
||
46 + [0x0a3c, 0x0a3c],
|
||
47 + [0x0a41, 0x0a42],
|
||
48 + [0x0a47, 0x0a48],
|
||
49 + [0x0a4b, 0x0a4d],
|
||
50 + [0x0a51, 0x0a51],
|
||
51 + [0x0a70, 0x0a71],
|
||
52 + [0x0a75, 0x0a75],
|
||
53 + [0x0a81, 0x0a82],
|
||
54 + [0x0abc, 0x0abc],
|
||
55 + [0x0ac1, 0x0ac5],
|
||
56 + [0x0ac7, 0x0ac8],
|
||
57 + [0x0acd, 0x0acd],
|
||
58 + [0x0ae2, 0x0ae3],
|
||
59 + [0x0b01, 0x0b01],
|
||
60 + [0x0b3c, 0x0b3c],
|
||
61 + [0x0b3f, 0x0b3f],
|
||
62 + [0x0b41, 0x0b44],
|
||
63 + [0x0b4d, 0x0b4d],
|
||
64 + [0x0b56, 0x0b56],
|
||
65 + [0x0b62, 0x0b63],
|
||
66 + [0x0b82, 0x0b82],
|
||
67 + [0x0bc0, 0x0bc0],
|
||
68 + [0x0bcd, 0x0bcd],
|
||
69 + [0x0c00, 0x0c00],
|
||
70 + [0x0c3e, 0x0c40],
|
||
71 + [0x0c46, 0x0c48],
|
||
72 + [0x0c4a, 0x0c4d],
|
||
73 + [0x0c55, 0x0c56],
|
||
74 + [0x0c62, 0x0c63],
|
||
75 + [0x0c81, 0x0c81],
|
||
76 + [0x0cbc, 0x0cbc],
|
||
77 + [0x0cbf, 0x0cbf],
|
||
78 + [0x0cc6, 0x0cc6],
|
||
79 + [0x0ccc, 0x0ccd],
|
||
80 + [0x0ce2, 0x0ce3],
|
||
81 + [0x0d00, 0x0d01],
|
||
82 + [0x0d3b, 0x0d3c],
|
||
83 + [0x0d41, 0x0d44],
|
||
84 + [0x0d4d, 0x0d4d],
|
||
85 + [0x0d62, 0x0d63],
|
||
86 + [0x0dca, 0x0dca],
|
||
87 + [0x0dd2, 0x0dd4],
|
||
88 + [0x0dd6, 0x0dd6],
|
||
89 + [0x0e31, 0x0e31],
|
||
90 + [0x0e34, 0x0e3a],
|
||
91 + [0x0e47, 0x0e4e],
|
||
92 + [0x0eb1, 0x0eb1],
|
||
93 + [0x0eb4, 0x0ebc],
|
||
94 + [0x0ec8, 0x0ecd],
|
||
95 + [0x0f18, 0x0f19],
|
||
96 + [0x0f35, 0x0f35],
|
||
97 + [0x0f37, 0x0f37],
|
||
98 + [0x0f39, 0x0f39],
|
||
99 + [0x0f71, 0x0f7e],
|
||
100 + [0x0f80, 0x0f84],
|
||
101 + [0x0f86, 0x0f87],
|
||
102 + [0x0f8d, 0x0f97],
|
||
103 + [0x0f99, 0x0fbc],
|
||
104 + [0x0fc6, 0x0fc6],
|
||
105 + [0x102d, 0x1030],
|
||
106 + [0x1032, 0x1037],
|
||
107 + [0x1039, 0x103a],
|
||
108 + [0x103d, 0x103e],
|
||
109 + [0x1058, 0x1059],
|
||
110 + [0x105e, 0x1060],
|
||
111 + [0x1071, 0x1074],
|
||
112 + [0x1082, 0x1082],
|
||
113 + [0x1085, 0x1086],
|
||
114 + [0x108d, 0x108d],
|
||
115 + [0x109d, 0x109d],
|
||
116 + [0x135d, 0x135f],
|
||
117 + [0x1712, 0x1714],
|
||
118 + [0x1732, 0x1734],
|
||
119 + [0x1752, 0x1753],
|
||
120 + [0x1772, 0x1773],
|
||
121 + [0x17b4, 0x17b5],
|
||
122 + [0x17b7, 0x17bd],
|
||
123 + [0x17c6, 0x17c6],
|
||
124 + [0x17c9, 0x17d3],
|
||
125 + [0x17dd, 0x17dd],
|
||
126 + [0x180b, 0x180d],
|
||
127 + [0x1885, 0x1886],
|
||
128 + [0x18a9, 0x18a9],
|
||
129 + [0x1920, 0x1922],
|
||
130 + [0x1927, 0x1928],
|
||
131 + [0x1932, 0x1932],
|
||
132 + [0x1939, 0x193b],
|
||
133 + [0x1a17, 0x1a18],
|
||
134 + [0x1a1b, 0x1a1b],
|
||
135 + [0x1a56, 0x1a56],
|
||
136 + [0x1a58, 0x1a5e],
|
||
137 + [0x1a60, 0x1a60],
|
||
138 + [0x1a62, 0x1a62],
|
||
139 + [0x1a65, 0x1a6c],
|
||
140 + [0x1a73, 0x1a7c],
|
||
141 + [0x1a7f, 0x1a7f],
|
||
142 + [0x1ab0, 0x1ace],
|
||
143 + [0x1b00, 0x1b03],
|
||
144 + [0x1b34, 0x1b34],
|
||
145 + [0x1b36, 0x1b3a],
|
||
146 + [0x1b3c, 0x1b3c],
|
||
147 + [0x1b42, 0x1b42],
|
||
148 + [0x1b6b, 0x1b73],
|
||
149 + [0x1b80, 0x1b81],
|
||
150 + [0x1ba2, 0x1ba5],
|
||
151 + [0x1ba8, 0x1ba9],
|
||
152 + [0x1bab, 0x1bad],
|
||
153 + [0x1be6, 0x1be6],
|
||
154 + [0x1be8, 0x1be9],
|
||
155 + [0x1bed, 0x1bed],
|
||
156 + [0x1bef, 0x1bf1],
|
||
157 + [0x1c2c, 0x1c33],
|
||
158 + [0x1c36, 0x1c37],
|
||
159 + [0x1cd0, 0x1cd2],
|
||
160 + [0x1cd4, 0x1ce0],
|
||
161 + [0x1ce2, 0x1ce8],
|
||
162 + [0x1ced, 0x1ced],
|
||
163 + [0x1cf4, 0x1cf4],
|
||
164 + [0x1cf8, 0x1cf9],
|
||
165 + [0x1dc0, 0x1df9],
|
||
166 + [0x1dfb, 0x1dff],
|
||
167 + [0x200b, 0x200f],
|
||
168 + [0x202a, 0x202e],
|
||
169 + [0x2060, 0x2064],
|
||
170 + [0x2066, 0x206f],
|
||
171 + [0x20d0, 0x20f0],
|
||
172 + [0x2cef, 0x2cf1],
|
||
173 + [0x2d7f, 0x2d7f],
|
||
174 + [0x2de0, 0x2dff],
|
||
175 + [0x302a, 0x302f],
|
||
176 + [0x3099, 0x309a],
|
||
177 + [0xa66f, 0xa672],
|
||
178 + [0xa674, 0xa67d],
|
||
179 + [0xa69e, 0xa69f],
|
||
180 + [0xa6f0, 0xa6f1],
|
||
181 + [0xa802, 0xa802],
|
||
182 + [0xa806, 0xa806],
|
||
183 + [0xa80b, 0xa80b],
|
||
184 + [0xa825, 0xa826],
|
||
185 + [0xa8c4, 0xa8c5],
|
||
186 + [0xa8e0, 0xa8f1],
|
||
187 + [0xa926, 0xa92d],
|
||
188 + [0xa947, 0xa951],
|
||
189 + [0xa980, 0xa982],
|
||
190 + [0xa9b3, 0xa9b3],
|
||
191 + [0xa9b6, 0xa9b9],
|
||
192 + [0xa9bc, 0xa9bc],
|
||
193 + [0xa9e5, 0xa9e5],
|
||
194 + [0xaa29, 0xaa2e],
|
||
195 + [0xaa31, 0xaa32],
|
||
196 + [0xaa35, 0xaa36],
|
||
197 + [0xaa43, 0xaa43],
|
||
198 + [0xaa4c, 0xaa4c],
|
||
199 + [0xaa7c, 0xaa7c],
|
||
200 + [0xaab0, 0xaab0],
|
||
201 + [0xaab2, 0xaab4],
|
||
202 + [0xaab7, 0xaab8],
|
||
203 + [0xaabe, 0xaabf],
|
||
204 + [0xaac1, 0xaac1],
|
||
205 + [0xaaec, 0xaaed],
|
||
206 + [0xaaf6, 0xaaf6],
|
||
207 + [0xabe5, 0xabe5],
|
||
208 + [0xabe8, 0xabe8],
|
||
209 + [0xabed, 0xabed],
|
||
210 + [0xfb1e, 0xfb1e],
|
||
211 + [0xfe00, 0xfe0f],
|
||
212 + [0xfe20, 0xfe2f],
|
||
213 + [0xfeff, 0xfeff],
|
||
214 + [0xfff9, 0xfffb],
|
||
215 + [0x101fd, 0x101fd],
|
||
216 + [0x102e0, 0x102e0],
|
||
217 + [0x10376, 0x1037a],
|
||
218 + [0x10a01, 0x10a03],
|
||
219 + [0x10a05, 0x10a06],
|
||
220 + [0x10a0c, 0x10a0f],
|
||
221 + [0x10a38, 0x10a3a],
|
||
222 + [0x10a3f, 0x10a3f],
|
||
223 + [0x10ae5, 0x10ae6],
|
||
224 + [0x10d24, 0x10d27],
|
||
225 + [0x10eab, 0x10eac],
|
||
226 + [0x10f46, 0x10f50],
|
||
227 + [0x11001, 0x11001],
|
||
228 + [0x11038, 0x11046],
|
||
229 + [0x1107f, 0x11081],
|
||
230 + [0x110b3, 0x110b6],
|
||
231 + [0x110b9, 0x110ba],
|
||
232 + [0x110c2, 0x110c2],
|
||
233 + [0x11100, 0x11102],
|
||
234 + [0x11127, 0x1112b],
|
||
235 + [0x1112d, 0x11134],
|
||
236 + [0x11173, 0x11173],
|
||
237 + [0x11180, 0x11181],
|
||
238 + [0x111b6, 0x111be],
|
||
239 + [0x111c9, 0x111cc],
|
||
240 + [0x1122f, 0x11231],
|
||
241 + [0x11234, 0x11234],
|
||
242 + [0x11236, 0x11237],
|
||
243 + [0x1123e, 0x1123e],
|
||
244 + [0x112df, 0x112df],
|
||
245 + [0x112e3, 0x112ea],
|
||
246 + [0x11300, 0x11301],
|
||
247 + [0x1133b, 0x1133c],
|
||
248 + [0x11340, 0x11340],
|
||
249 + [0x11366, 0x1136c],
|
||
250 + [0x11370, 0x11374],
|
||
251 + [0x11438, 0x1143f],
|
||
252 + [0x11442, 0x11444],
|
||
253 + [0x11446, 0x11446],
|
||
254 + [0x1145e, 0x1145e],
|
||
255 + [0x114b3, 0x114b8],
|
||
256 + [0x114ba, 0x114ba],
|
||
257 + [0x114bf, 0x114c0],
|
||
258 + [0x114c2, 0x114c3],
|
||
259 + [0x115b2, 0x115b5],
|
||
260 + [0x115bc, 0x115bd],
|
||
261 + [0x115bf, 0x115c0],
|
||
262 + [0x115dc, 0x115dd],
|
||
263 + [0x11633, 0x1163a],
|
||
264 + [0x1163d, 0x1163d],
|
||
265 + [0x1163f, 0x11640],
|
||
266 + [0x116ab, 0x116ab],
|
||
267 + [0x116ad, 0x116ad],
|
||
268 + [0x116b0, 0x116b5],
|
||
269 + [0x116b7, 0x116b7],
|
||
270 + [0x1171d, 0x1171f],
|
||
271 + [0x11722, 0x11725],
|
||
272 + [0x11727, 0x1172b],
|
||
273 + [0x1182f, 0x11837],
|
||
274 + [0x11839, 0x1183a],
|
||
275 + [0x1193b, 0x1193c],
|
||
276 + [0x1193e, 0x1193e],
|
||
277 + [0x11943, 0x11943],
|
||
278 + [0x119d4, 0x119d7],
|
||
279 + [0x119da, 0x119db],
|
||
280 + [0x119e0, 0x119e0],
|
||
281 + [0x11a01, 0x11a0a],
|
||
282 + [0x11a33, 0x11a38],
|
||
283 + [0x11a3b, 0x11a3e],
|
||
284 + [0x11a47, 0x11a47],
|
||
285 + [0x11a51, 0x11a56],
|
||
286 + [0x11a59, 0x11a5b],
|
||
287 + [0x11a8a, 0x11a96],
|
||
288 + [0x11a98, 0x11a99],
|
||
289 + [0x11c30, 0x11c36],
|
||
290 + [0x11c38, 0x11c3d],
|
||
291 + [0x11c3f, 0x11c3f],
|
||
292 + [0x11c92, 0x11ca7],
|
||
293 + [0x11caa, 0x11cb0],
|
||
294 + [0x11cb2, 0x11cb3],
|
||
295 + [0x11cb5, 0x11cb6],
|
||
296 + [0x11d31, 0x11d36],
|
||
297 + [0x11d3a, 0x11d3a],
|
||
298 + [0x11d3c, 0x11d3d],
|
||
299 + [0x11d3f, 0x11d45],
|
||
300 + [0x11d47, 0x11d47],
|
||
301 + [0x11d90, 0x11d91],
|
||
302 + [0x11ef3, 0x11ef4],
|
||
303 + [0x16af0, 0x16af4],
|
||
304 + [0x16b30, 0x16b36],
|
||
305 + [0x16f4f, 0x16f4f],
|
||
306 + [0x16f8f, 0x16f92],
|
||
307 + [0x16fe4, 0x16fe4],
|
||
308 + [0x1bc9d, 0x1bc9e],
|
||
309 + [0x1cf00, 0x1cf2d],
|
||
310 + [0x1cf30, 0x1cf46],
|
||
311 + [0x1d167, 0x1d169],
|
||
312 + [0x1d17b, 0x1d182],
|
||
313 + [0x1d185, 0x1d18b],
|
||
314 + [0x1d1aa, 0x1d1ad],
|
||
315 + [0x1d242, 0x1d244],
|
||
316 + [0xe0100, 0xe01ef],
|
||
317 +];
|
||
318
|
||
⋮
|
||
335
|
||
336 +function isCombining(codePoint) {
|
||
337 + for (const [start, end] of COMBINING_RANGES) {
|
||
338 + if (codePoint >= start && codePoint <= end) return true;
|
||
339 + }
|
||
340 + return false;
|
||
341 +}
|
||
342 +
|
||
343 +function isFullWidth(codePoint) {
|
||
344 + if (codePoint >= 0x1100 && (
|
||
345 + codePoint <= 0x115f ||
|
||
346 + codePoint === 0x2329 ||
|
||
347 + codePoint === 0x232a ||
|
||
348 + (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
||
349 + (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
||
350 + (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
||
351 + (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
||
352 + (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
||
353 + (codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
||
354 + (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
||
355 + (codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
||
356 + )) {
|
||
357 + return true;
|
||
358 + }
|
||
359 + return false;
|
||
360 +}
|
||
361 +
|
||
362 +function visibleWidth(text) {
|
||
363 + const str = stripAnsi(text);
|
||
364 + let width = 0;
|
||
365 + for (let i = 0; i < str.length; i += 1) {
|
||
366 + const codePoint = str.codePointAt(i);
|
||
367 + if (codePoint > 0xffff) i += 1;
|
||
368 + if (codePoint === 0) continue;
|
||
369 + if (codePoint < 32 || (codePoint >= 0x7f && codePoint <= 0x9f)) continue;
|
||
370 + if (isCombining(codePoint)) continue;
|
||
371 + width += isFullWidth(codePoint) ? 2 : 1;
|
||
372 + }
|
||
373 + return width;
|
||
374 +}
|
||
375 +
|
||
376 function truncatePlain(text, maxCols) {
|
||
⋮
|
||
378 if (maxCols <= 0) return '';
|
||
30 - if (str.length <= maxCols) return str;
|
||
31 - if (maxCols <= 3) return str.slice(0, maxCols);
|
||
32 - return `${str.slice(0, maxCols - 3)}...`;
|
||
379 + if (visibleWidth(str) <= maxCols) return str;
|
||
380 + const useEllipsis = maxCols > 3;
|
||
381 + const limit = maxCols - (useEllipsis ? 3 : 0);
|
||
382 + let out = '';
|
||
383 + let width = 0;
|
||
384 + for (let i = 0; i < str.length && width < limit; i += 1) {
|
||
385 + const codePoint = str.codePointAt(i);
|
||
386 + if (codePoint > 0xffff) i += 1;
|
||
387 + if (codePoint === 0) continue;
|
||
388 + if (codePoint < 32 || (codePoint >= 0x7f && codePoint <= 0x9f)) continue;
|
||
389 + if (isCombining(codePoint)) continue;
|
||
390 + const nextWidth = isFullWidth(codePoint) ? 2 : 1;
|
||
391 + if (width + nextWidth > limit) break;
|
||
392 + out += String.fromCodePoint(codePoint);
|
||
393 + width += nextWidth;
|
||
394 + }
|
||
395 + if (useEllipsis) out += '...';
|
||
396 + return out;
|
||
397 }
|
||
⋮
|
||
401 if (maxCols <= 0) return '';
|
||
38 - if (stripAnsi(str).length <= maxCols) return str;
|
||
402 + if (visibleWidth(str) <= maxCols) return str;
|
||
403 const useEllipsis = maxCols > 3;
|
||
⋮
|
||
405 let out = '';
|
||
42 - let visible = 0;
|
||
406 + let width = 0;
|
||
407 const ansiRegex = new RegExp(ANSI_REGEX.source, 'y');
|
||
44 - for (let i = 0; i < str.length && visible < limit; ) {
|
||
408 + for (let i = 0; i < str.length && width < limit; ) {
|
||
409 ansiRegex.lastIndex = i;
|
||
⋮
|
||
415 }
|
||
52 - out += str[i];
|
||
53 - visible += 1;
|
||
54 - i += 1;
|
||
416 + const codePoint = str.codePointAt(i);
|
||
417 + const char = String.fromCodePoint(codePoint);
|
||
418 + if (codePoint > 0xffff) i += 2;
|
||
419 + else i += 1;
|
||
420 + if (codePoint === 0) continue;
|
||
421 + if (codePoint < 32 || (codePoint >= 0x7f && codePoint <= 0x9f)) continue;
|
||
422 + if (isCombining(codePoint)) continue;
|
||
423 + const nextWidth = isFullWidth(codePoint) ? 2 : 1;
|
||
424 + if (width + nextWidth > limit) break;
|
||
425 + out += char;
|
||
426 + width += nextWidth;
|
||
427 }
|
||
⋮
|
||
447 const firstLine = parts[0] ?? '';
|
||
76 - const labelLen = stripAnsi(label).length;
|
||
77 - const suffixLen = stripAnsi(suffix).length;
|
||
448 + const labelLen = visibleWidth(label);
|
||
449 + const suffixLen = visibleWidth(suffix);
|
||
450 const available = width - labelLen - 1 - suffixLen;
|
||
⋮
|
||
521 process.stdout.write(lineText);
|
||
150 - const lineLen = stripAnsi(lineText).length;
|
||
522 + const lineLen = visibleWidth(lineText);
|
||
523 lastLines = Math.max(1, Math.ceil(lineLen / Math.max(1, width)));
|
||
|
||
• Edited prompts/system.txt (+1 -0)
|
||
2 目标:帮助用户完成开发任务,优先高信息密度输出。
|
||
3 +输出限制:禁止使用 Markdown(md)格式,内容无法渲染,必须使用纯文字格式输出。
|
||
|
||
• Edited src/core/context.js (+16 -11)
|
||
34 const systemName = platform === 'darwin' ? 'macos' : platform === 'win32' ? 'windows' : 'linux';
|
||
35 - const lines = [
|
||
36 - basePrompt.trim(),
|
||
37 - '',
|
||
38 - `时间: ${now.toISOString()}`,
|
||
39 - `工作区: ${opts.workspace}`,
|
||
40 - `系统: ${systemName} (${os.release()})`,
|
||
41 - `终端: ${getTerminalType()}`,
|
||
42 - `权限: ${opts.allowMode}`,
|
||
43 - `Git: ${getGitInfo(opts.workspace)}`,
|
||
44 - ];
|
||
45 - return lines.join('\n');
|
||
35 + const replacements = {
|
||
36 + current_time: now.toISOString(),
|
||
37 + path: opts.workspace,
|
||
38 + workspace: opts.workspace,
|
||
39 + system: `${systemName} (${os.release()})`,
|
||
40 + terminal: getTerminalType(),
|
||
41 + allow_mode: opts.allowMode,
|
||
42 + permissions: opts.allowMode,
|
||
43 + git: getGitInfo(opts.workspace),
|
||
44 + };
|
||
45 + let prompt = basePrompt;
|
||
46 + for (const [key, value] of Object.entries(replacements)) {
|
||
47 + const token = `{${key}}`;
|
||
48 + prompt = prompt.split(token).join(String(value));
|
||
49 + }
|
||
50 + return prompt.trim();
|
||
51 }
|
||
|
||
• Edited prompts/system.txt (+7 -0)
|
||
3 输出限制:禁止使用 Markdown(md)格式,内容无法渲染,必须使用纯文字格式输出。
|
||
4 +
|
||
5 +- 当前时间:{current_time}
|
||
6 +- 工作区路径:{path}
|
||
7 +- 系统信息:{system}
|
||
8 +- 终端类型:{terminal}
|
||
9 +- 权限:{allow_mode}
|
||
10 +- Git:{git}
|
||
|
||
• Edited src/core/context.js (+4 -1)
|
||
32 const now = new Date();
|
||
33 + const tzOffset = now.getTimezoneOffset();
|
||
34 + const localMs = now.getTime() - tzOffset * 60 * 1000;
|
||
35 + const localIso = new Date(localMs).toISOString().slice(0, 16);
|
||
36 const platform = os.platform();
|
||
⋮
|
||
38 const replacements = {
|
||
36 - current_time: now.toISOString(),
|
||
39 + current_time: localIso,
|
||
40 path: opts.workspace,
|
||
|
||
• Edited prompts/system.txt (+1 -0)
|
||
9 - 权限:{allow_mode}
|
||
10 +- 只读限制说明:{allow_note}
|
||
11 - Git:{git}
|
||
|
||
• Edited src/core/context.js (+14 -1)
|
||
35 const localIso = new Date(localMs).toISOString().slice(0, 16);
|
||
36 + const allowNote = opts.allowMode === 'read_only'
|
||
37 + ? '已禁用 edit_file、run_command。若用户要求使用,请告知当前无权限,需要请求切换权限。'
|
||
38 + : '';
|
||
39 const platform = os.platform();
|
||
40 const systemName = platform === 'darwin' ? 'macos' : platform === 'win32' ? 'windows' : 'linux';
|
||
41 + let prompt = basePrompt;
|
||
42 + if (allowNote) {
|
||
43 + prompt = prompt.split('{allow_note}').join(allowNote);
|
||
44 + } else {
|
||
45 + const marker = '__ALLOW_NOTE__';
|
||
46 + prompt = prompt.split('{allow_note}').join(marker);
|
||
47 + prompt = prompt
|
||
48 + .split('\n')
|
||
49 + .filter((line) => !line.includes(marker))
|
||
50 + .join('\n');
|
||
51 + }
|
||
52 const replacements = {
|
||
⋮
|
||
61 };
|
||
48 - let prompt = basePrompt;
|
||
62 for (const [key, value] of Object.entries(replacements)) {
|
||
|
||
• Edited src/tools/dispatcher.js (+3 -2)
|
||
155 if (allowMode === 'read_only' && (name === 'edit_file' || name === 'run_command')) {
|
||
156 + const note = '当前为只读模式,已禁用 edit_file、run_command。若需使用,请先请求切换权限。';
|
||
157 return {
|
||
⋮
|
||
159 tool: name,
|
||
159 - error: '当前为只读模式,禁止修改文件或运行指令',
|
||
160 - formatted: '当前为只读模式,禁止修改文件或运行指令',
|
||
160 + error: note,
|
||
161 + formatted: note,
|
||
162 };
|
||
|
||
• Edited prompts/system.txt (+0 -1)
|
||
9 - 权限:{allow_mode}
|
||
10 -- 只读限制说明:{allow_note}
|
||
10 - Git:{git}
|
||
|
||
• Edited src/core/context.js (+5 -15)
|
||
35 const localIso = new Date(localMs).toISOString().slice(0, 16);
|
||
36 - const allowNote = opts.allowMode === 'read_only'
|
||
37 - ? '已禁用 edit_file、run_command。若用户要求使用,请告知当前无权限,需要请求切换权限。'
|
||
38 - : '';
|
||
36 const platform = os.platform();
|
||
⋮
|
||
38 let prompt = basePrompt;
|
||
42 - if (allowNote) {
|
||
43 - prompt = prompt.split('{allow_note}').join(allowNote);
|
||
44 - } else {
|
||
45 - const marker = '__ALLOW_NOTE__';
|
||
46 - prompt = prompt.split('{allow_note}').join(marker);
|
||
47 - prompt = prompt
|
||
48 - .split('\n')
|
||
49 - .filter((line) => !line.includes(marker))
|
||
50 - .join('\n');
|
||
51 - }
|
||
39 + const allowModeValue = opts.allowMode === 'read_only'
|
||
40 + ? `${opts.allowMode}\n 已禁用 edit_file、run_command。若用户要求使用,请告知当前无权限需要请求切换权限。`
|
||
41 + : opts.allowMode;
|
||
42 const replacements = {
|
||
⋮
|
||
47 terminal: getTerminalType(),
|
||
58 - allow_mode: opts.allowMode,
|
||
59 - permissions: opts.allowMode,
|
||
48 + allow_mode: allowModeValue,
|
||
49 + permissions: allowModeValue,
|
||
50 git: getGitInfo(opts.workspace),
|
||
|
||
• Edited src/ui/select_prompt.js (+3 -1)
|
||
25 },
|
||
26 - });
|
||
26 + }, { clearPromptOnDone: true });
|
||
27 + readline.clearLine(process.stdout, 0);
|
||
28 + readline.cursorTo(process.stdout, 0);
|
||
29 return value;
|
||
|
||
• Edited src/ui/tool_display.js (+1 -410)
|
||
7 const DOT_OFF = '◦';
|
||
8 -const ANSI_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
||
9 -const COMBINING_RANGES = [
|
||
10 - [0x0300, 0x036f],
|
||
11 - [0x0483, 0x0489],
|
||
12 - [0x0591, 0x05bd],
|
||
13 - [0x05bf, 0x05bf],
|
||
14 - [0x05c1, 0x05c2],
|
||
15 - [0x05c4, 0x05c5],
|
||
16 - [0x05c7, 0x05c7],
|
||
17 - [0x0610, 0x061a],
|
||
18 - [0x064b, 0x065f],
|
||
19 - [0x0670, 0x0670],
|
||
20 - [0x06d6, 0x06dc],
|
||
21 - [0x06df, 0x06e4],
|
||
22 - [0x06e7, 0x06e8],
|
||
23 - [0x06ea, 0x06ed],
|
||
24 - [0x0711, 0x0711],
|
||
25 - [0x0730, 0x074a],
|
||
26 - [0x07a6, 0x07b0],
|
||
27 - [0x07eb, 0x07f3],
|
||
28 - [0x0816, 0x0819],
|
||
29 - [0x081b, 0x0823],
|
||
30 - [0x0825, 0x0827],
|
||
31 - [0x0829, 0x082d],
|
||
32 - [0x0859, 0x085b],
|
||
33 - [0x0900, 0x0902],
|
||
34 - [0x093a, 0x093a],
|
||
35 - [0x093c, 0x093c],
|
||
36 - [0x0941, 0x0948],
|
||
37 - [0x094d, 0x094d],
|
||
38 - [0x0951, 0x0957],
|
||
39 - [0x0962, 0x0963],
|
||
40 - [0x0981, 0x0981],
|
||
41 - [0x09bc, 0x09bc],
|
||
42 - [0x09c1, 0x09c4],
|
||
43 - [0x09cd, 0x09cd],
|
||
44 - [0x09e2, 0x09e3],
|
||
45 - [0x0a01, 0x0a02],
|
||
46 - [0x0a3c, 0x0a3c],
|
||
47 - [0x0a41, 0x0a42],
|
||
48 - [0x0a47, 0x0a48],
|
||
49 - [0x0a4b, 0x0a4d],
|
||
50 - [0x0a51, 0x0a51],
|
||
51 - [0x0a70, 0x0a71],
|
||
52 - [0x0a75, 0x0a75],
|
||
53 - [0x0a81, 0x0a82],
|
||
54 - [0x0abc, 0x0abc],
|
||
55 - [0x0ac1, 0x0ac5],
|
||
56 - [0x0ac7, 0x0ac8],
|
||
57 - [0x0acd, 0x0acd],
|
||
58 - [0x0ae2, 0x0ae3],
|
||
59 - [0x0b01, 0x0b01],
|
||
60 - [0x0b3c, 0x0b3c],
|
||
61 - [0x0b3f, 0x0b3f],
|
||
62 - [0x0b41, 0x0b44],
|
||
63 - [0x0b4d, 0x0b4d],
|
||
64 - [0x0b56, 0x0b56],
|
||
65 - [0x0b62, 0x0b63],
|
||
66 - [0x0b82, 0x0b82],
|
||
67 - [0x0bc0, 0x0bc0],
|
||
68 - [0x0bcd, 0x0bcd],
|
||
69 - [0x0c00, 0x0c00],
|
||
70 - [0x0c3e, 0x0c40],
|
||
71 - [0x0c46, 0x0c48],
|
||
72 - [0x0c4a, 0x0c4d],
|
||
73 - [0x0c55, 0x0c56],
|
||
74 - [0x0c62, 0x0c63],
|
||
75 - [0x0c81, 0x0c81],
|
||
76 - [0x0cbc, 0x0cbc],
|
||
77 - [0x0cbf, 0x0cbf],
|
||
78 - [0x0cc6, 0x0cc6],
|
||
79 - [0x0ccc, 0x0ccd],
|
||
80 - [0x0ce2, 0x0ce3],
|
||
81 - [0x0d00, 0x0d01],
|
||
82 - [0x0d3b, 0x0d3c],
|
||
83 - [0x0d41, 0x0d44],
|
||
84 - [0x0d4d, 0x0d4d],
|
||
85 - [0x0d62, 0x0d63],
|
||
86 - [0x0dca, 0x0dca],
|
||
87 - [0x0dd2, 0x0dd4],
|
||
88 - [0x0dd6, 0x0dd6],
|
||
89 - [0x0e31, 0x0e31],
|
||
90 - [0x0e34, 0x0e3a],
|
||
91 - [0x0e47, 0x0e4e],
|
||
92 - [0x0eb1, 0x0eb1],
|
||
93 - [0x0eb4, 0x0ebc],
|
||
94 - [0x0ec8, 0x0ecd],
|
||
95 - [0x0f18, 0x0f19],
|
||
96 - [0x0f35, 0x0f35],
|
||
97 - [0x0f37, 0x0f37],
|
||
98 - [0x0f39, 0x0f39],
|
||
99 - [0x0f71, 0x0f7e],
|
||
100 - [0x0f80, 0x0f84],
|
||
101 - [0x0f86, 0x0f87],
|
||
102 - [0x0f8d, 0x0f97],
|
||
103 - [0x0f99, 0x0fbc],
|
||
104 - [0x0fc6, 0x0fc6],
|
||
105 - [0x102d, 0x1030],
|
||
106 - [0x1032, 0x1037],
|
||
107 - [0x1039, 0x103a],
|
||
108 - [0x103d, 0x103e],
|
||
109 - [0x1058, 0x1059],
|
||
110 - [0x105e, 0x1060],
|
||
111 - [0x1071, 0x1074],
|
||
112 - [0x1082, 0x1082],
|
||
113 - [0x1085, 0x1086],
|
||
114 - [0x108d, 0x108d],
|
||
115 - [0x109d, 0x109d],
|
||
116 - [0x135d, 0x135f],
|
||
117 - [0x1712, 0x1714],
|
||
118 - [0x1732, 0x1734],
|
||
119 - [0x1752, 0x1753],
|
||
120 - [0x1772, 0x1773],
|
||
121 - [0x17b4, 0x17b5],
|
||
122 - [0x17b7, 0x17bd],
|
||
123 - [0x17c6, 0x17c6],
|
||
124 - [0x17c9, 0x17d3],
|
||
125 - [0x17dd, 0x17dd],
|
||
126 - [0x180b, 0x180d],
|
||
127 - [0x1885, 0x1886],
|
||
128 - [0x18a9, 0x18a9],
|
||
129 - [0x1920, 0x1922],
|
||
130 - [0x1927, 0x1928],
|
||
131 - [0x1932, 0x1932],
|
||
132 - [0x1939, 0x193b],
|
||
133 - [0x1a17, 0x1a18],
|
||
134 - [0x1a1b, 0x1a1b],
|
||
135 - [0x1a56, 0x1a56],
|
||
136 - [0x1a58, 0x1a5e],
|
||
137 - [0x1a60, 0x1a60],
|
||
138 - [0x1a62, 0x1a62],
|
||
139 - [0x1a65, 0x1a6c],
|
||
140 - [0x1a73, 0x1a7c],
|
||
141 - [0x1a7f, 0x1a7f],
|
||
142 - [0x1ab0, 0x1ace],
|
||
143 - [0x1b00, 0x1b03],
|
||
144 - [0x1b34, 0x1b34],
|
||
145 - [0x1b36, 0x1b3a],
|
||
146 - [0x1b3c, 0x1b3c],
|
||
147 - [0x1b42, 0x1b42],
|
||
148 - [0x1b6b, 0x1b73],
|
||
149 - [0x1b80, 0x1b81],
|
||
150 - [0x1ba2, 0x1ba5],
|
||
151 - [0x1ba8, 0x1ba9],
|
||
152 - [0x1bab, 0x1bad],
|
||
153 - [0x1be6, 0x1be6],
|
||
154 - [0x1be8, 0x1be9],
|
||
155 - [0x1bed, 0x1bed],
|
||
156 - [0x1bef, 0x1bf1],
|
||
157 - [0x1c2c, 0x1c33],
|
||
158 - [0x1c36, 0x1c37],
|
||
159 - [0x1cd0, 0x1cd2],
|
||
160 - [0x1cd4, 0x1ce0],
|
||
161 - [0x1ce2, 0x1ce8],
|
||
162 - [0x1ced, 0x1ced],
|
||
163 - [0x1cf4, 0x1cf4],
|
||
164 - [0x1cf8, 0x1cf9],
|
||
165 - [0x1dc0, 0x1df9],
|
||
166 - [0x1dfb, 0x1dff],
|
||
167 - [0x200b, 0x200f],
|
||
168 - [0x202a, 0x202e],
|
||
169 - [0x2060, 0x2064],
|
||
170 - [0x2066, 0x206f],
|
||
171 - [0x20d0, 0x20f0],
|
||
172 - [0x2cef, 0x2cf1],
|
||
173 - [0x2d7f, 0x2d7f],
|
||
174 - [0x2de0, 0x2dff],
|
||
175 - [0x302a, 0x302f],
|
||
176 - [0x3099, 0x309a],
|
||
177 - [0xa66f, 0xa672],
|
||
178 - [0xa674, 0xa67d],
|
||
179 - [0xa69e, 0xa69f],
|
||
180 - [0xa6f0, 0xa6f1],
|
||
181 - [0xa802, 0xa802],
|
||
182 - [0xa806, 0xa806],
|
||
183 - [0xa80b, 0xa80b],
|
||
184 - [0xa825, 0xa826],
|
||
185 - [0xa8c4, 0xa8c5],
|
||
186 - [0xa8e0, 0xa8f1],
|
||
187 - [0xa926, 0xa92d],
|
||
188 - [0xa947, 0xa951],
|
||
189 - [0xa980, 0xa982],
|
||
190 - [0xa9b3, 0xa9b3],
|
||
191 - [0xa9b6, 0xa9b9],
|
||
192 - [0xa9bc, 0xa9bc],
|
||
193 - [0xa9e5, 0xa9e5],
|
||
194 - [0xaa29, 0xaa2e],
|
||
195 - [0xaa31, 0xaa32],
|
||
196 - [0xaa35, 0xaa36],
|
||
197 - [0xaa43, 0xaa43],
|
||
198 - [0xaa4c, 0xaa4c],
|
||
199 - [0xaa7c, 0xaa7c],
|
||
200 - [0xaab0, 0xaab0],
|
||
201 - [0xaab2, 0xaab4],
|
||
202 - [0xaab7, 0xaab8],
|
||
203 - [0xaabe, 0xaabf],
|
||
204 - [0xaac1, 0xaac1],
|
||
205 - [0xaaec, 0xaaed],
|
||
206 - [0xaaf6, 0xaaf6],
|
||
207 - [0xabe5, 0xabe5],
|
||
208 - [0xabe8, 0xabe8],
|
||
209 - [0xabed, 0xabed],
|
||
210 - [0xfb1e, 0xfb1e],
|
||
211 - [0xfe00, 0xfe0f],
|
||
212 - [0xfe20, 0xfe2f],
|
||
213 - [0xfeff, 0xfeff],
|
||
214 - [0xfff9, 0xfffb],
|
||
215 - [0x101fd, 0x101fd],
|
||
216 - [0x102e0, 0x102e0],
|
||
217 - [0x10376, 0x1037a],
|
||
218 - [0x10a01, 0x10a03],
|
||
219 - [0x10a05, 0x10a06],
|
||
220 - [0x10a0c, 0x10a0f],
|
||
221 - [0x10a38, 0x10a3a],
|
||
222 - [0x10a3f, 0x10a3f],
|
||
223 - [0x10ae5, 0x10ae6],
|
||
224 - [0x10d24, 0x10d27],
|
||
225 - [0x10eab, 0x10eac],
|
||
226 - [0x10f46, 0x10f50],
|
||
227 - [0x11001, 0x11001],
|
||
228 - [0x11038, 0x11046],
|
||
229 - [0x1107f, 0x11081],
|
||
230 - [0x110b3, 0x110b6],
|
||
231 - [0x110b9, 0x110ba],
|
||
232 - [0x110c2, 0x110c2],
|
||
233 - [0x11100, 0x11102],
|
||
234 - [0x11127, 0x1112b],
|
||
235 - [0x1112d, 0x11134],
|
||
236 - [0x11173, 0x11173],
|
||
237 - [0x11180, 0x11181],
|
||
238 - [0x111b6, 0x111be],
|
||
239 - [0x111c9, 0x111cc],
|
||
240 - [0x1122f, 0x11231],
|
||
241 - [0x11234, 0x11234],
|
||
242 - [0x11236, 0x11237],
|
||
243 - [0x1123e, 0x1123e],
|
||
244 - [0x112df, 0x112df],
|
||
245 - [0x112e3, 0x112ea],
|
||
246 - [0x11300, 0x11301],
|
||
247 - [0x1133b, 0x1133c],
|
||
248 - [0x11340, 0x11340],
|
||
249 - [0x11366, 0x1136c],
|
||
250 - [0x11370, 0x11374],
|
||
251 - [0x11438, 0x1143f],
|
||
252 - [0x11442, 0x11444],
|
||
253 - [0x11446, 0x11446],
|
||
254 - [0x1145e, 0x1145e],
|
||
255 - [0x114b3, 0x114b8],
|
||
256 - [0x114ba, 0x114ba],
|
||
257 - [0x114bf, 0x114c0],
|
||
258 - [0x114c2, 0x114c3],
|
||
259 - [0x115b2, 0x115b5],
|
||
260 - [0x115bc, 0x115bd],
|
||
261 - [0x115bf, 0x115c0],
|
||
262 - [0x115dc, 0x115dd],
|
||
263 - [0x11633, 0x1163a],
|
||
264 - [0x1163d, 0x1163d],
|
||
265 - [0x1163f, 0x11640],
|
||
266 - [0x116ab, 0x116ab],
|
||
267 - [0x116ad, 0x116ad],
|
||
268 - [0x116b0, 0x116b5],
|
||
269 - [0x116b7, 0x116b7],
|
||
270 - [0x1171d, 0x1171f],
|
||
271 - [0x11722, 0x11725],
|
||
272 - [0x11727, 0x1172b],
|
||
273 - [0x1182f, 0x11837],
|
||
274 - [0x11839, 0x1183a],
|
||
275 - [0x1193b, 0x1193c],
|
||
276 - [0x1193e, 0x1193e],
|
||
277 - [0x11943, 0x11943],
|
||
278 - [0x119d4, 0x119d7],
|
||
279 - [0x119da, 0x119db],
|
||
280 - [0x119e0, 0x119e0],
|
||
281 - [0x11a01, 0x11a0a],
|
||
282 - [0x11a33, 0x11a38],
|
||
283 - [0x11a3b, 0x11a3e],
|
||
284 - [0x11a47, 0x11a47],
|
||
285 - [0x11a51, 0x11a56],
|
||
286 - [0x11a59, 0x11a5b],
|
||
287 - [0x11a8a, 0x11a96],
|
||
288 - [0x11a98, 0x11a99],
|
||
289 - [0x11c30, 0x11c36],
|
||
290 - [0x11c38, 0x11c3d],
|
||
291 - [0x11c3f, 0x11c3f],
|
||
292 - [0x11c92, 0x11ca7],
|
||
293 - [0x11caa, 0x11cb0],
|
||
294 - [0x11cb2, 0x11cb3],
|
||
295 - [0x11cb5, 0x11cb6],
|
||
296 - [0x11d31, 0x11d36],
|
||
297 - [0x11d3a, 0x11d3a],
|
||
298 - [0x11d3c, 0x11d3d],
|
||
299 - [0x11d3f, 0x11d45],
|
||
300 - [0x11d47, 0x11d47],
|
||
301 - [0x11d90, 0x11d91],
|
||
302 - [0x11ef3, 0x11ef4],
|
||
303 - [0x16af0, 0x16af4],
|
||
304 - [0x16b30, 0x16b36],
|
||
305 - [0x16f4f, 0x16f4f],
|
||
306 - [0x16f8f, 0x16f92],
|
||
307 - [0x16fe4, 0x16fe4],
|
||
308 - [0x1bc9d, 0x1bc9e],
|
||
309 - [0x1cf00, 0x1cf2d],
|
||
310 - [0x1cf30, 0x1cf46],
|
||
311 - [0x1d167, 0x1d169],
|
||
312 - [0x1d17b, 0x1d182],
|
||
313 - [0x1d185, 0x1d18b],
|
||
314 - [0x1d1aa, 0x1d1ad],
|
||
315 - [0x1d242, 0x1d244],
|
||
316 - [0xe0100, 0xe01ef],
|
||
317 -];
|
||
8 +const { stripAnsi, visibleWidth, truncatePlain, truncateVisible } = require('../utils/text_width');
|
||
9
|
||
⋮
|
||
20 return map[name] || name;
|
||
330 -}
|
||
331 -
|
||
332 -function stripAnsi(text) {
|
||
333 - return String(text ?? '').replace(ANSI_REGEX, '');
|
||
334 -}
|
||
335 -
|
||
336 -function isCombining(codePoint) {
|
||
337 - for (const [start, end] of COMBINING_RANGES) {
|
||
338 - if (codePoint >= start && codePoint <= end) return true;
|
||
339 - }
|
||
340 - return false;
|
||
341 -}
|
||
342 -
|
||
343 -function isFullWidth(codePoint) {
|
||
344 - if (codePoint >= 0x1100 && (
|
||
345 - codePoint <= 0x115f ||
|
||
346 - codePoint === 0x2329 ||
|
||
347 - codePoint === 0x232a ||
|
||
348 - (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
||
349 - (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
||
350 - (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
||
351 - (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
||
352 - (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
||
353 - (codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
||
354 - (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
||
355 - (codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
||
356 - )) {
|
||
357 - return true;
|
||
358 - }
|
||
359 - return false;
|
||
360 -}
|
||
361 -
|
||
362 -function visibleWidth(text) {
|
||
363 - const str = stripAnsi(text);
|
||
364 - let width = 0;
|
||
365 - for (let i = 0; i < str.length; i += 1) {
|
||
366 - const codePoint = str.codePointAt(i);
|
||
367 - if (codePoint > 0xffff) i += 1;
|
||
368 - if (codePoint === 0) continue;
|
||
369 - if (codePoint < 32 || (codePoint >= 0x7f && codePoint <= 0x9f)) continue;
|
||
370 - if (isCombining(codePoint)) continue;
|
||
371 - width += isFullWidth(codePoint) ? 2 : 1;
|
||
372 - }
|
||
373 - return width;
|
||
374 -}
|
||
375 -
|
||
376 -function truncatePlain(text, maxCols) {
|
||
377 - const str = String(text ?? '');
|
||
378 - if (maxCols <= 0) return '';
|
||
379 - if (visibleWidth(str) <= maxCols) return str;
|
||
380 - const useEllipsis = maxCols > 3;
|
||
381 - const limit = maxCols - (useEllipsis ? 3 : 0);
|
||
382 - let out = '';
|
||
383 - let width = 0;
|
||
384 - for (let i = 0; i < str.length && width < limit; i += 1) {
|
||
385 - const codePoint = str.codePointAt(i);
|
||
386 - if (codePoint > 0xffff) i += 1;
|
||
387 - if (codePoint === 0) continue;
|
||
388 - if (codePoint < 32 || (codePoint >= 0x7f && codePoint <= 0x9f)) continue;
|
||
389 - if (isCombining(codePoint)) continue;
|
||
390 - const nextWidth = isFullWidth(codePoint) ? 2 : 1;
|
||
391 - if (width + nextWidth > limit) break;
|
||
392 - out += String.fromCodePoint(codePoint);
|
||
393 - width += nextWidth;
|
||
394 - }
|
||
395 - if (useEllipsis) out += '...';
|
||
396 - return out;
|
||
21 }
|
||
22
|
||
399 -function truncateVisible(text, maxCols) {
|
||
400 - const str = String(text ?? '');
|
||
401 - if (maxCols <= 0) return '';
|
||
402 - if (visibleWidth(str) <= maxCols) return str;
|
||
403 - const useEllipsis = maxCols > 3;
|
||
404 - const limit = maxCols - (useEllipsis ? 3 : 0);
|
||
405 - let out = '';
|
||
406 - let width = 0;
|
||
407 - const ansiRegex = new RegExp(ANSI_REGEX.source, 'y');
|
||
408 - for (let i = 0; i < str.length && width < limit; ) {
|
||
409 - ansiRegex.lastIndex = i;
|
||
410 - const match = ansiRegex.exec(str);
|
||
411 - if (match && match.index === i) {
|
||
412 - out += match[0];
|
||
413 - i += match[0].length;
|
||
414 - continue;
|
||
415 - }
|
||
416 - const codePoint = str.codePointAt(i);
|
||
417 - const char = String.fromCodePoint(codePoint);
|
||
418 - if (codePoint > 0xffff) i += 2;
|
||
419 - else i += 1;
|
||
420 - if (codePoint === 0) continue;
|
||
421 - if (codePoint < 32 || (codePoint >= 0x7f && codePoint <= 0x9f)) continue;
|
||
422 - if (isCombining(codePoint)) continue;
|
||
423 - const nextWidth = isFullWidth(codePoint) ? 2 : 1;
|
||
424 - if (width + nextWidth > limit) break;
|
||
425 - out += char;
|
||
426 - width += nextWidth;
|
||
427 - }
|
||
428 - if (useEllipsis) out += '...';
|
||
429 - if (str.includes('\x1b[')) out += '\x1b[0m';
|
||
430 - return out;
|
||
431 -}
|
||
23
|
||
|
||
• Edited src/cli/index.js (+6 -4)
|
||
16 const { Spinner, truncateThinking } = require('../ui/spinner');
|
||
17 +const { renderBanner } = require('../ui/banner');
|
||
18 const { buildStartLine, buildFinalLine, startToolDisplay, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
⋮
|
||
41
|
||
41 -console.log('');
|
||
42 -console.log('eagent');
|
||
43 -console.log(`model: ${state.modelKey} | allow: ${state.allowMode} | cwd: ${WORKSPACE_NAME}`);
|
||
44 -console.log('');
|
||
42 +renderBanner({
|
||
43 + modelKey: state.modelKey,
|
||
44 + workspace: WORKSPACE,
|
||
45 + conversationId: state.conversation?.id,
|
||
46 +});
|
||
47
|
||
|
||
• Edited src/ui/banner.js (+19 -13)
|
||
5
|
||
6 -function renderBanner({ modelKey, workspace, conversationId }) {
|
||
6 +function renderBox({ title, lines }) {
|
||
7 const cols = Number(process.stdout.columns) || 80;
|
||
8 - const title = `>_ Welcome to ${bold('EasyAgent')}`;
|
||
9 - const lines = [
|
||
10 - `model: ${modelKey || ''}`,
|
||
11 - `path: ${workspace || ''}`,
|
||
12 - `conversation: ${conversationId || 'none'}`,
|
||
13 - ];
|
||
8 + const safeTitle = title ? String(title) : '';
|
||
9 + const safeLines = Array.isArray(lines) ? lines.map((line) => String(line)) : [];
|
||
10 if (cols < 20) {
|
||
11 console.log('');
|
||
16 - console.log(title);
|
||
17 - lines.forEach((line) => console.log(line));
|
||
12 + if (safeTitle) console.log(safeTitle);
|
||
13 + safeLines.forEach((line) => console.log(line));
|
||
14 console.log('');
|
||
⋮
|
||
16 }
|
||
21 - const contentWidth = Math.max(visibleWidth(title), ...lines.map(visibleWidth));
|
||
17 + const contentWidth = Math.max(visibleWidth(safeTitle), ...safeLines.map(visibleWidth));
|
||
18 const innerWidth = Math.max(10, Math.min(cols - 2, contentWidth + 2));
|
||
⋮
|
||
23 console.log(top);
|
||
28 - console.log(renderLine(title));
|
||
29 - lines.forEach((line) => console.log(renderLine(line)));
|
||
24 + if (safeTitle) console.log(renderLine(safeTitle));
|
||
25 + safeLines.forEach((line) => console.log(renderLine(line)));
|
||
26 console.log(top);
|
||
⋮
|
||
29
|
||
34 -module.exports = { renderBanner };
|
||
30 +function renderBanner({ modelKey, workspace, conversationId }) {
|
||
31 + const title = `>_ Welcome to ${bold('EasyAgent')}`;
|
||
32 + const lines = [
|
||
33 + `model: ${modelKey || ''}`,
|
||
34 + `path: ${workspace || ''}`,
|
||
35 + `conversation: ${conversationId || 'none'}`,
|
||
36 + ];
|
||
37 + renderBox({ title, lines });
|
||
38 +}
|
||
39 +
|
||
40 +module.exports = { renderBanner, renderBox };
|
||
|
||
• Edited src/cli/commands.js (+11 -5)
|
||
8 const { buildFinalLine, formatResultLines, printResultLines } = require('../ui/tool_display');
|
||
9 +const { renderBox } = require('../ui/banner');
|
||
10 const { createIndentedWriter } = require('../ui/indented_writer');
|
||
⋮
|
||
178 if (cmd === '/status') {
|
||
178 - console.log(`model: ${state.modelKey} | 思考: ${state.thinkingMode ? 'thinking' : 'fast'}`);
|
||
179 - console.log(`workspace: ${workspace}`);
|
||
180 - console.log(`allow: ${state.allowMode}`);
|
||
181 - console.log(`conversation: ${state.conversation?.id || 'none'}`);
|
||
182 - console.log(`token usage: ${state.tokenUsage}`);
|
||
179 + const title = 'Status';
|
||
180 + const lines = [
|
||
181 + `model: ${state.modelKey}`,
|
||
182 + `thinking: ${state.thinkingMode ? 'thinking' : 'fast'}`,
|
||
183 + `workspace: ${workspace}`,
|
||
184 + `allow: ${state.allowMode}`,
|
||
185 + `conversation: ${state.conversation?.id || 'none'}`,
|
||
186 + `token usage: ${state.tokenUsage}`,
|
||
187 + ];
|
||
188 + renderBox({ title, lines });
|
||
189 return { exit: false };
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
25 const USERNAME = os.userInfo().username || 'user';
|
||
26 -const PROMPT = `${USERNAME}@${WORKSPACE_NAME} `;
|
||
26 +const PROMPT = `${USERNAME}@${WORKSPACE_NAME} % `;
|
||
27
|
||
|
||
• Edited src/core/state.js (+1 -1)
|
||
9 thinkingMode: true,
|
||
10 - tokenUsage: 0,
|
||
10 + tokenUsage: { prompt: 0, completion: 0, total: 0 },
|
||
11 conversation: null,
|
||
|
||
• Edited src/storage/conversation_store.js (+5 -1)
|
||
6 const { toISO } = require('../utils/time');
|
||
7 +const { normalizeTokenUsage } = require('../utils/token_usage');
|
||
8
|
||
⋮
|
||
85 allow_mode: metadata.allow_mode || 'full_access',
|
||
85 - token_usage: metadata.token_usage || 0,
|
||
86 + token_usage: normalizeTokenUsage(metadata.token_usage),
|
||
87 cwd: metadata.cwd || '',
|
||
⋮
|
||
126 ...metadataUpdates,
|
||
127 + token_usage: metadataUpdates && Object.prototype.hasOwnProperty.call(metadataUpdates, 'token_usage')
|
||
128 + ? normalizeTokenUsage(metadataUpdates.token_usage)
|
||
129 + : normalizeTokenUsage(conversation.metadata?.token_usage),
|
||
130 };
|
||
|
||
• Edited src/cli/commands.js (+7 -3)
|
||
11 const { cyan, green } = require('../utils/colors');
|
||
12 +const { normalizeTokenUsage } = require('../utils/token_usage');
|
||
13 const { Spinner } = require('../ui/spinner');
|
||
⋮
|
||
79 state.allowMode = conv.metadata?.allow_mode || state.allowMode;
|
||
79 - state.tokenUsage = conv.metadata?.token_usage || 0;
|
||
80 + state.tokenUsage = normalizeTokenUsage(conv.metadata?.token_usage);
|
||
81 console.log(`已加载对话: ${conv.id}`);
|
||
⋮
|
||
129 state.allowMode = conv.metadata?.allow_mode || state.allowMode;
|
||
129 - state.tokenUsage = conv.metadata?.token_usage || 0;
|
||
130 + state.tokenUsage = normalizeTokenUsage(conv.metadata?.token_usage);
|
||
131 console.log(`已加载对话: ${conv.id}`);
|
||
⋮
|
||
179 if (cmd === '/status') {
|
||
180 + const usage = normalizeTokenUsage(state.tokenUsage);
|
||
181 const title = 'Status';
|
||
⋮
|
||
187 `conversation: ${state.conversation?.id || 'none'}`,
|
||
186 - `token usage: ${state.tokenUsage}`,
|
||
188 + `tokens(in): ${usage.prompt}`,
|
||
189 + `tokens(out): ${usage.completion}`,
|
||
190 + `tokens(total): ${usage.total}`,
|
||
191 ];
|
||
|
||
• Edited src/cli/index.js (+16 -2)
|
||
19 const { createConversation, updateConversation } = require('../storage/conversation_store');
|
||
20 +const { applyUsage, normalizeTokenUsage } = require('../utils/token_usage');
|
||
21 const { gray, cyan, green } = require('../utils/colors');
|
||
⋮
|
||
238 let usageTotal = null;
|
||
239 + let usagePrompt = null;
|
||
240 + let usageCompletion = null;
|
||
241 let firstContent = true;
|
||
⋮
|
||
258 if (!choice) continue;
|
||
256 - if (chunk.usage && chunk.usage.total_tokens) usageTotal = chunk.usage.total_tokens;
|
||
259 + const usage = (choice && (choice.usage || choice.delta?.usage)) || chunk.usage;
|
||
260 + if (usage) {
|
||
261 + if (Number.isFinite(usage.prompt_tokens)) usagePrompt = usage.prompt_tokens;
|
||
262 + if (Number.isFinite(usage.completion_tokens)) usageCompletion = usage.completion_tokens;
|
||
263 + if (Number.isFinite(usage.total_tokens)) usageTotal = usage.total_tokens;
|
||
264 + }
|
||
265 const delta = choice.delta || {};
|
||
⋮
|
||
332
|
||
325 - if (usageTotal) state.tokenUsage = usageTotal;
|
||
333 + if (usageTotal || usagePrompt || usageCompletion) {
|
||
334 + state.tokenUsage = applyUsage(normalizeTokenUsage(state.tokenUsage), {
|
||
335 + prompt_tokens: usagePrompt,
|
||
336 + completion_tokens: usageCompletion,
|
||
337 + total_tokens: usageTotal,
|
||
338 + });
|
||
339 + }
|
||
340
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
332
|
||
333 - if (usageTotal || usagePrompt || usageCompletion) {
|
||
333 + if (usageTotal !== null || usagePrompt !== null || usageCompletion !== null) {
|
||
334 state.tokenUsage = applyUsage(normalizeTokenUsage(state.tokenUsage), {
|
||
|
||
• Edited src/cli/commands.js (+3 -3)
|
||
187 `conversation: ${state.conversation?.id || 'none'}`,
|
||
188 - `tokens(in): ${usage.prompt}`,
|
||
189 - `tokens(out): ${usage.completion}`,
|
||
190 - `tokens(total): ${usage.total}`,
|
||
188 + `输入token: ${usage.prompt}`,
|
||
189 + `输出token: ${usage.completion}`,
|
||
190 + `当前上下文总token: ${usage.total}`,
|
||
191 ];
|
||
|
||
• Edited src/cli/index.js (+5 -1)
|
||
51 let menuSearchTerm = '';
|
||
52 +let menuLastSearchTerm = '';
|
||
53 let menuJustClosedAt = 0;
|
||
⋮
|
||
94 menuJustClosedAt = menuInjectedCommand ? Date.now() : 0;
|
||
95 + menuLastSearchTerm = menuSearchTerm;
|
||
96 drainStdin();
|
||
⋮
|
||
135 const tooOld = Date.now() - menuJustClosedAt > 800;
|
||
134 - const normalizedMenu = String(menuSearchTerm).trim().replace(/^\/+/, '');
|
||
136 + const normalizedMenu = String(menuLastSearchTerm).trim().replace(/^\/+/, '');
|
||
137 const normalizedInput = input.replace(/^\/+/, '');
|
||
⋮
|
||
139 menuJustClosedAt = 0;
|
||
140 + menuLastSearchTerm = '';
|
||
141 rl.prompt();
|
||
⋮
|
||
144 menuJustClosedAt = 0;
|
||
145 + menuLastSearchTerm = '';
|
||
146 }
|
||
|
||
• Edited src/cli/index.js (+7 -0)
|
||
72 commandMenuActive = true;
|
||
73 + if (rl) {
|
||
74 + rl.pause();
|
||
75 + rl.line = '';
|
||
76 + rl.cursor = 0;
|
||
77 + readline.clearLine(process.stdout, 0);
|
||
78 + readline.cursorTo(process.stdout, 0);
|
||
79 + }
|
||
80 menuSearchTerm = '';
|
||
|
||
• Edited src/cli/index.js (+5 -0)
|
||
147 menuLastSearchTerm = '';
|
||
148 + if (process.stdout.isTTY) {
|
||
149 + readline.moveCursor(process.stdout, 0, -1);
|
||
150 + readline.clearLine(process.stdout, 0);
|
||
151 + readline.cursorTo(process.stdout, 0);
|
||
152 + }
|
||
153 rl.prompt();
|
||
|
||
• Edited src/cli/index.js (+44 -0)
|
||
27 const PROMPT = `${USERNAME}@${WORKSPACE_NAME} % `;
|
||
28 +const DEBUG_LOG_ENABLED = process.env.EAGENT_DEBUG === '1';
|
||
29 +const DEBUG_LOG_PATH = path.join(WORKSPACE, 'eagent-debug.log');
|
||
30 +
|
||
31 +function debugLog(event, data = {}) {
|
||
32 + if (!DEBUG_LOG_ENABLED) return;
|
||
33 + try {
|
||
34 + const payload = {
|
||
35 + ts: new Date().toISOString(),
|
||
36 + event,
|
||
37 + ...data,
|
||
38 + };
|
||
39 + fs.appendFileSync(DEBUG_LOG_PATH, `${JSON.stringify(payload)}\n`);
|
||
40 + } catch (_) {}
|
||
41 +}
|
||
42
|
||
⋮
|
||
61 });
|
||
62 +debugLog('startup', { prompt: PROMPT, pid: process.pid });
|
||
63
|
||
⋮
|
||
79 if (key && key.name === 'backspace' && menuSearchTerm === '') {
|
||
80 + debugLog('menu_backspace_abort', { str, key: key.name });
|
||
81 if (menuAbortController && !menuAbortController.signal.aborted) {
|
||
⋮
|
||
87 if (str === '/' && (rl.line === '' || rl.line === '/')) {
|
||
88 + debugLog('menu_open_requested', { str, rl_line: rl.line });
|
||
89 commandMenuActive = true;
|
||
⋮
|
||
106 menuSearchTerm = input || '';
|
||
107 + debugLog('menu_input', { input: menuSearchTerm });
|
||
108 },
|
||
⋮
|
||
111 .then((result) => {
|
||
112 + debugLog('menu_result', { result });
|
||
113 if (result && result.chosen && !result.cancelled) {
|
||
⋮
|
||
117 .finally(() => {
|
||
118 + debugLog('menu_close_before', {
|
||
119 + injected: menuInjectedCommand,
|
||
120 + lastTerm: menuLastSearchTerm,
|
||
121 + searchTerm: menuSearchTerm,
|
||
122 + });
|
||
123 commandMenuActive = false;
|
||
⋮
|
||
136 menuInjectedCommand = null;
|
||
137 + debugLog('menu_inject', { injected });
|
||
138 setImmediate(() => injectLine(injected));
|
||
139 }
|
||
140 + debugLog('menu_close_after', {
|
||
141 + justClosedAt: menuJustClosedAt,
|
||
142 + lastTerm: menuLastSearchTerm,
|
||
143 + });
|
||
144 });
|
||
⋮
|
||
164 rl.on('line', async (line) => {
|
||
165 + debugLog('line', {
|
||
166 + line,
|
||
167 + commandMenuActive,
|
||
168 + menuJustClosedAt,
|
||
169 + menuLastSearchTerm,
|
||
170 + menuSearchTerm,
|
||
171 + });
|
||
172 if (commandMenuActive) {
|
||
⋮
|
||
180 const normalizedInput = input.replace(/^\/+/, '');
|
||
181 + const willConsume = !tooOld && (!normalizedMenu ? input === '' : normalizedInput === normalizedMenu);
|
||
182 + debugLog('menu_close_check', {
|
||
183 + tooOld,
|
||
184 + normalizedMenu,
|
||
185 + normalizedInput,
|
||
186 + willConsume,
|
||
187 + });
|
||
188 if (!tooOld && (!normalizedMenu ? input === '' : normalizedInput === normalizedMenu)) {
|
||
⋮
|
||
237 if (!rl) return;
|
||
238 + debugLog('inject_line', { text });
|
||
239 rl.write(text);
|
||
|
||
• Edited src/cli/index.js (+5 -1)
|
||
171 });
|
||
172 + if (rl) {
|
||
173 + rl.line = '';
|
||
174 + rl.cursor = 0;
|
||
175 + }
|
||
176 if (commandMenuActive) {
|
||
173 - rl.prompt();
|
||
177 + debugLog('line_ignored_menu', { line });
|
||
178 return;
|
||
|
||
• Edited src/cli/index.js (+3 -0)
|
||
130 menuSearchTerm = '';
|
||
131 + if (process.stdout.isTTY) {
|
||
132 + readline.clearScreenDown(process.stdout);
|
||
133 + }
|
||
134 readline.clearLine(process.stdout, 0);
|
||
|
||
• Edited src/cli/index.js (+27 -3)
|
||
29 const DEBUG_LOG_PATH = path.join(WORKSPACE, 'eagent-debug.log');
|
||
30 +const MENU_PAGE_SIZE = 6;
|
||
31
|
||
⋮
|
||
62 });
|
||
62 -debugLog('startup', { prompt: PROMPT, pid: process.pid });
|
||
63 +debugLog('startup', {
|
||
64 + prompt: PROMPT,
|
||
65 + pid: process.pid,
|
||
66 + tty: process.stdout.isTTY,
|
||
67 + columns: process.stdout.columns,
|
||
68 + rows: process.stdout.rows,
|
||
69 +});
|
||
70
|
||
⋮
|
||
94 if (str === '/' && (rl.line === '' || rl.line === '/')) {
|
||
88 - debugLog('menu_open_requested', { str, rl_line: rl.line });
|
||
95 + debugLog('menu_open_requested', {
|
||
96 + str,
|
||
97 + rl_line: rl.line,
|
||
98 + rl_cursor: rl.cursor,
|
||
99 + tty: process.stdout.isTTY,
|
||
100 + columns: process.stdout.columns,
|
||
101 + rows: process.stdout.rows,
|
||
102 + });
|
||
103 commandMenuActive = true;
|
||
⋮
|
||
115 prompt: PROMPT,
|
||
102 - pageSize: 6,
|
||
116 + pageSize: MENU_PAGE_SIZE,
|
||
117 colorEnabled: process.stdout.isTTY,
|
||
⋮
|
||
135 searchTerm: menuSearchTerm,
|
||
136 + tty: process.stdout.isTTY,
|
||
137 + columns: process.stdout.columns,
|
||
138 + rows: process.stdout.rows,
|
||
139 });
|
||
⋮
|
||
148 if (process.stdout.isTTY) {
|
||
149 + debugLog('menu_close_clear', { method: 'clearScreenDown' });
|
||
150 readline.clearScreenDown(process.stdout);
|
||
⋮
|
||
153 readline.cursorTo(process.stdout, 0);
|
||
154 + debugLog('prompt', { reason: 'menu_close' });
|
||
155 rl.prompt(true);
|
||
⋮
|
||
217 if (process.stdout.isTTY) {
|
||
218 + debugLog('menu_close_clear', { method: 'moveCursorUp+clearLine' });
|
||
219 readline.moveCursor(process.stdout, 0, -1);
|
||
⋮
|
||
222 }
|
||
223 + debugLog('prompt', { reason: 'menu_close_consume' });
|
||
224 rl.prompt();
|
||
⋮
|
||
230 if (!input) {
|
||
231 + debugLog('prompt', { reason: 'empty_line' });
|
||
232 rl.prompt();
|
||
⋮
|
||
238 if (result && result.exit) return;
|
||
239 + debugLog('prompt', { reason: 'command_done', command: input });
|
||
240 rl.prompt();
|
||
⋮
|
||
249 await runAssistantLoop();
|
||
250 + debugLog('prompt', { reason: 'assistant_done' });
|
||
251 rl.prompt();
|
||
|
||
• Edited src/cli/index.js (+6 -1)
|
||
154 debugLog('prompt', { reason: 'menu_close' });
|
||
155 - rl.prompt(true);
|
||
155 + if (!menuInjectedCommand) {
|
||
156 + debugLog('prompt', { reason: 'menu_close' });
|
||
157 + rl.prompt(true);
|
||
158 + } else {
|
||
159 + debugLog('prompt', { reason: 'menu_close_skip_for_inject' });
|
||
160 + }
|
||
161 if (menuInjectedCommand) {
|
||
|
||
• Edited src/cli/index.js (+11 -7)
|
||
151 }
|
||
152 - readline.clearLine(process.stdout, 0);
|
||
153 - readline.cursorTo(process.stdout, 0);
|
||
154 - debugLog('prompt', { reason: 'menu_close' });
|
||
155 - if (!menuInjectedCommand) {
|
||
156 - debugLog('prompt', { reason: 'menu_close' });
|
||
157 - rl.prompt(true);
|
||
152 + // Clear possible echoes from the base readline line (current + previous line).
|
||
153 + if (process.stdout.isTTY) {
|
||
154 + readline.clearLine(process.stdout, 0);
|
||
155 + readline.cursorTo(process.stdout, 0);
|
||
156 + readline.moveCursor(process.stdout, 0, -1);
|
||
157 + readline.clearLine(process.stdout, 0);
|
||
158 + readline.cursorTo(process.stdout, 0);
|
||
159 } else {
|
||
159 - debugLog('prompt', { reason: 'menu_close_skip_for_inject' });
|
||
160 + readline.clearLine(process.stdout, 0);
|
||
161 + readline.cursorTo(process.stdout, 0);
|
||
162 }
|
||
163 + debugLog('prompt', { reason: menuInjectedCommand ? 'menu_close_before_inject' : 'menu_close' });
|
||
164 + rl.prompt(true);
|
||
165 if (menuInjectedCommand) {
|
||
|
||
• Edited src/cli/index.js (+0 -68)
|
||
27 const PROMPT = `${USERNAME}@${WORKSPACE_NAME} % `;
|
||
28 -const DEBUG_LOG_ENABLED = process.env.EAGENT_DEBUG === '1';
|
||
29 -const DEBUG_LOG_PATH = path.join(WORKSPACE, 'eagent-debug.log');
|
||
28 const MENU_PAGE_SIZE = 6;
|
||
29
|
||
32 -function debugLog(event, data = {}) {
|
||
33 - if (!DEBUG_LOG_ENABLED) return;
|
||
34 - try {
|
||
35 - const payload = {
|
||
36 - ts: new Date().toISOString(),
|
||
37 - event,
|
||
38 - ...data,
|
||
39 - };
|
||
40 - fs.appendFileSync(DEBUG_LOG_PATH, `${JSON.stringify(payload)}\n`);
|
||
41 - } catch (_) {}
|
||
42 -}
|
||
43 -
|
||
30 const config = ensureConfig();
|
||
⋮
|
||
47 conversationId: state.conversation?.id,
|
||
62 -});
|
||
63 -debugLog('startup', {
|
||
64 - prompt: PROMPT,
|
||
65 - pid: process.pid,
|
||
66 - tty: process.stdout.isTTY,
|
||
67 - columns: process.stdout.columns,
|
||
68 - rows: process.stdout.rows,
|
||
48 });
|
||
⋮
|
||
65 if (key && key.name === 'backspace' && menuSearchTerm === '') {
|
||
87 - debugLog('menu_backspace_abort', { str, key: key.name });
|
||
66 if (menuAbortController && !menuAbortController.signal.aborted) {
|
||
⋮
|
||
72 if (str === '/' && (rl.line === '' || rl.line === '/')) {
|
||
95 - debugLog('menu_open_requested', {
|
||
96 - str,
|
||
97 - rl_line: rl.line,
|
||
98 - rl_cursor: rl.cursor,
|
||
99 - tty: process.stdout.isTTY,
|
||
100 - columns: process.stdout.columns,
|
||
101 - rows: process.stdout.rows,
|
||
102 - });
|
||
73 commandMenuActive = true;
|
||
⋮
|
||
90 menuSearchTerm = input || '';
|
||
121 - debugLog('menu_input', { input: menuSearchTerm });
|
||
91 },
|
||
⋮
|
||
94 .then((result) => {
|
||
126 - debugLog('menu_result', { result });
|
||
95 if (result && result.chosen && !result.cancelled) {
|
||
⋮
|
||
99 .finally(() => {
|
||
132 - debugLog('menu_close_before', {
|
||
133 - injected: menuInjectedCommand,
|
||
134 - lastTerm: menuLastSearchTerm,
|
||
135 - searchTerm: menuSearchTerm,
|
||
136 - tty: process.stdout.isTTY,
|
||
137 - columns: process.stdout.columns,
|
||
138 - rows: process.stdout.rows,
|
||
139 - });
|
||
100 commandMenuActive = false;
|
||
⋮
|
||
108 if (process.stdout.isTTY) {
|
||
149 - debugLog('menu_close_clear', { method: 'clearScreenDown' });
|
||
109 readline.clearScreenDown(process.stdout);
|
||
⋮
|
||
121 }
|
||
163 - debugLog('prompt', { reason: menuInjectedCommand ? 'menu_close_before_inject' : 'menu_close' });
|
||
122 rl.prompt(true);
|
||
⋮
|
||
125 menuInjectedCommand = null;
|
||
168 - debugLog('menu_inject', { injected });
|
||
126 setImmediate(() => injectLine(injected));
|
||
127 }
|
||
171 - debugLog('menu_close_after', {
|
||
172 - justClosedAt: menuJustClosedAt,
|
||
173 - lastTerm: menuLastSearchTerm,
|
||
174 - });
|
||
128 });
|
||
⋮
|
||
148 rl.on('line', async (line) => {
|
||
196 - debugLog('line', {
|
||
197 - line,
|
||
198 - commandMenuActive,
|
||
199 - menuJustClosedAt,
|
||
200 - menuLastSearchTerm,
|
||
201 - menuSearchTerm,
|
||
202 - });
|
||
149 if (rl) {
|
||
⋮
|
||
153 if (commandMenuActive) {
|
||
208 - debugLog('line_ignored_menu', { line });
|
||
154 return;
|
||
⋮
|
||
160 const normalizedInput = input.replace(/^\/+/, '');
|
||
216 - const willConsume = !tooOld && (!normalizedMenu ? input === '' : normalizedInput === normalizedMenu);
|
||
217 - debugLog('menu_close_check', {
|
||
218 - tooOld,
|
||
219 - normalizedMenu,
|
||
220 - normalizedInput,
|
||
221 - willConsume,
|
||
222 - });
|
||
161 if (!tooOld && (!normalizedMenu ? input === '' : normalizedInput === normalizedMenu)) {
|
||
⋮
|
||
164 if (process.stdout.isTTY) {
|
||
227 - debugLog('menu_close_clear', { method: 'moveCursorUp+clearLine' });
|
||
165 readline.moveCursor(process.stdout, 0, -1);
|
||
⋮
|
||
168 }
|
||
232 - debugLog('prompt', { reason: 'menu_close_consume' });
|
||
169 rl.prompt();
|
||
⋮
|
||
175 if (!input) {
|
||
240 - debugLog('prompt', { reason: 'empty_line' });
|
||
176 rl.prompt();
|
||
⋮
|
||
182 if (result && result.exit) return;
|
||
248 - debugLog('prompt', { reason: 'command_done', command: input });
|
||
183 rl.prompt();
|
||
⋮
|
||
192 await runAssistantLoop();
|
||
259 - debugLog('prompt', { reason: 'assistant_done' });
|
||
193 rl.prompt();
|
||
⋮
|
||
210 if (!rl) return;
|
||
278 - debugLog('inject_line', { text });
|
||
211 rl.write(text);
|
||
|
||
• Edited src/cli/index.js (+62 -1)
|
||
22 const { createIndentedWriter } = require('../ui/indented_writer');
|
||
23 +const { createStatusBar } = require('../ui/status_bar');
|
||
24
|
||
⋮
|
||
57 let menuAbortController = null;
|
||
58 +let statusBar = null;
|
||
59 +let escPendingCancel = false;
|
||
60 +let activeStreamController = null;
|
||
61
|
||
⋮
|
||
66
|
||
67 +statusBar = createStatusBar({
|
||
68 + getTokens: () => normalizeTokenUsage(state.tokenUsage).total || 0,
|
||
69 + maxTokens: '256k',
|
||
70 +});
|
||
71 +statusBar.render();
|
||
72 +
|
||
73 process.stdin.on('keypress', (str, key) => {
|
||
⋮
|
||
81 }
|
||
82 + if (key && key.name === 'escape') {
|
||
83 + escPendingCancel = true;
|
||
84 + if (activeStreamController && typeof activeStreamController.abort === 'function') {
|
||
85 + activeStreamController.abort();
|
||
86 + }
|
||
87 + return;
|
||
88 + }
|
||
89 if (str === '/' && (rl.line === '' || rl.line === '/')) {
|
||
⋮
|
||
165 rl.on('line', async (line) => {
|
||
166 + if (statusBar) statusBar.render();
|
||
167 if (rl) {
|
||
⋮
|
||
170 }
|
||
171 + if (escPendingCancel) {
|
||
172 + escPendingCancel = false;
|
||
173 + rl.prompt();
|
||
174 + return;
|
||
175 + }
|
||
176 if (commandMenuActive) {
|
||
⋮
|
||
282 while (continueLoop) {
|
||
283 + if (statusBar) statusBar.setMode('running');
|
||
284 hideCursor();
|
||
⋮
|
||
311 try {
|
||
288 - for await (const chunk of streamChat({ config, messages, tools, thinkingMode: state.thinkingMode })) {
|
||
312 + activeStreamController = new AbortController();
|
||
313 + for await (const chunk of streamChat({
|
||
314 + config,
|
||
315 + messages,
|
||
316 + tools,
|
||
317 + thinkingMode: state.thinkingMode,
|
||
318 + abortSignal: activeStreamController.signal,
|
||
319 + })) {
|
||
320 const choice = chunk.choices && chunk.choices[0];
|
||
⋮
|
||
372 } catch (err) {
|
||
373 + activeStreamController = null;
|
||
374 clearTimeout(thinkingDelay);
|
||
⋮
|
||
380 showCursor();
|
||
381 + if (escPendingCancel) {
|
||
382 + escPendingCancel = false;
|
||
383 + statusBar.setMode('input');
|
||
384 + console.log('已取消本次响应');
|
||
385 + return;
|
||
386 + }
|
||
387 console.log(`错误: ${err.message || err}`);
|
||
⋮
|
||
390
|
||
391 + activeStreamController = null;
|
||
392 +
|
||
393 + if (escPendingCancel) {
|
||
394 + escPendingCancel = false;
|
||
395 + clearTimeout(thinkingDelay);
|
||
396 + if (state.thinkingMode) {
|
||
397 + spinner.stop('○');
|
||
398 + } else {
|
||
399 + spinner.stopSilent();
|
||
400 + }
|
||
401 + showCursor();
|
||
402 + statusBar.setMode('input');
|
||
403 + console.log('已取消本次响应');
|
||
404 + return;
|
||
405 + }
|
||
406 +
|
||
407 clearTimeout(thinkingDelay);
|
||
⋮
|
||
441 for (const call of toolCallList) {
|
||
442 + if (escPendingCancel) {
|
||
443 + escPendingCancel = false;
|
||
444 + statusBar.setMode('input');
|
||
445 + console.log('工具被用户取消');
|
||
446 + return;
|
||
447 + }
|
||
448 let args = {};
|
||
⋮
|
||
480 showCursor();
|
||
481 + if (statusBar) statusBar.setMode('input');
|
||
482 }
|
||
|
||
• Edited src/model/client.js (+7 -1)
|
||
4
|
||
5 -async function* streamChat({ config, messages, tools, thinkingMode }) {
|
||
5 +async function* streamChat({ config, messages, tools, thinkingMode, abortSignal }) {
|
||
6 const profile = getModelProfile(config);
|
||
⋮
|
||
33 body: JSON.stringify(payload),
|
||
34 + signal: abortSignal,
|
||
35 });
|
||
36 } catch (err) {
|
||
37 + if (abortSignal && abortSignal.aborted) {
|
||
38 + const error = new Error('请求已取消');
|
||
39 + error.code = 'aborted';
|
||
40 + throw error;
|
||
41 + }
|
||
42 const cause = err && err.cause ? ` (${err.cause.code || err.cause.message || err.cause})` : '';
|
||
|
||
• Edited src/cli/index.js (+1 -0)
|
||
88 }
|
||
89 + if (statusBar) statusBar.render();
|
||
90 if (str === '/' && (rl.line === '' || rl.line === '/')) {
|
||
|
||
• Edited src/cli/index.js (+0 -2)
|
||
213 userWriter.writeLine(`${cyan('用户:')}${line}`);
|
||
214 - state.messages.push({ role: 'user', content: line });
|
||
215 - persistConversation();
|
||
214 await runAssistantLoop();
|
||
|
||
• Edited src/cli/index.js (+11 -1)
|
||
280 const showCursor = () => process.stdout.write('\x1b[?25h');
|
||
281 + let localMessages = null;
|
||
282 while (continueLoop) {
|
||
⋮
|
||
309
|
||
309 - const messages = buildApiMessages();
|
||
310 + if (!localMessages) {
|
||
311 + localMessages = buildApiMessages();
|
||
312 + }
|
||
313 + const messages = localMessages;
|
||
314 try {
|
||
⋮
|
||
429
|
||
430 + const userMsg = localMessages && localMessages[localMessages.length - 1];
|
||
431 + if (userMsg && userMsg.role === 'user') {
|
||
432 + state.messages.push(userMsg);
|
||
433 + persistConversation();
|
||
434 + }
|
||
435 +
|
||
436 const toolCallList = Object.keys(toolCalls)
|
||
|
||
• Edited src/cli/index.js (+20 -7)
|
||
213 userWriter.writeLine(`${cyan('用户:')}${line}`);
|
||
214 - await runAssistantLoop();
|
||
214 + await runAssistantLoop(line);
|
||
215 rl.prompt();
|
||
⋮
|
||
275
|
||
276 -async function runAssistantLoop() {
|
||
276 +async function runAssistantLoop(userInput) {
|
||
277 let continueLoop = true;
|
||
⋮
|
||
311 localMessages = buildApiMessages();
|
||
312 + if (userInput) {
|
||
313 + localMessages.push({ role: 'user', content: userInput });
|
||
314 + }
|
||
315 }
|
||
⋮
|
||
449 if (state.thinkingMode) assistantMsg.reasoning_content = fullThinkingBuffer || '';
|
||
447 - state.messages.push(assistantMsg);
|
||
448 - persistConversation();
|
||
450 + const toolMsgs = [];
|
||
451
|
||
⋮
|
||
464 const toolResult = await executeTool({ workspace: WORKSPACE, config, allowMode: state.allowMode, toolCall: call });
|
||
465 + if (escPendingCancel) {
|
||
466 + escPendingCancel = false;
|
||
467 + indicator.stop(finalLine);
|
||
468 + statusBar.setMode('input');
|
||
469 + console.log('工具被用户取消');
|
||
470 + return;
|
||
471 + }
|
||
472 indicator.stop(finalLine);
|
||
⋮
|
||
475
|
||
467 - const toolMsg = {
|
||
476 + toolMsgs.push({
|
||
477 role: 'tool',
|
||
⋮
|
||
480 tool_raw: toolResult.raw,
|
||
472 - };
|
||
481 + });
|
||
482 + }
|
||
483 +
|
||
484 + state.messages.push(assistantMsg);
|
||
485 + for (const toolMsg of toolMsgs) {
|
||
486 state.messages.push(toolMsg);
|
||
474 - persistConversation();
|
||
487 }
|
||
488 + persistConversation();
|
||
489 continueLoop = true;
|
||
|
||
• Edited src/cli/index.js (+0 -0)
|
||
|
||
• Edited src/model/client.js (+5 -0)
|
||
55 while (true) {
|
||
56 + if (abortSignal && abortSignal.aborted) {
|
||
57 + const error = new Error('请求已取消');
|
||
58 + error.code = 'aborted';
|
||
59 + throw error;
|
||
60 + }
|
||
61 const { value, done } = await reader.read();
|
||
|
||
• Edited src/cli/index.js (+2 -0)
|
||
369 assistantContent += delta.content;
|
||
370 + if (statusBar) statusBar.render();
|
||
371 }
|
||
⋮
|
||
475 printResultLines(resultLines);
|
||
476 + if (statusBar) statusBar.render();
|
||
477
|
||
|
||
• Edited src/cli/index.js (+4 -1)
|
||
60 let activeStreamController = null;
|
||
61 +let isRunning = false;
|
||
62
|
||
⋮
|
||
82 }
|
||
82 - if (key && key.name === 'escape') {
|
||
83 + if (key && key.name === 'escape' && isRunning) {
|
||
84 escPendingCancel = true;
|
||
⋮
|
||
282 let localMessages = null;
|
||
283 + isRunning = true;
|
||
284 while (continueLoop) {
|
||
⋮
|
||
507 if (statusBar) statusBar.setMode('input');
|
||
508 + isRunning = false;
|
||
509 }
|
||
|
||
• Edited src/cli/index.js (+10 -6)
|
||
283 isRunning = true;
|
||
284 + const finishRun = () => {
|
||
285 + if (statusBar) statusBar.setMode('input');
|
||
286 + isRunning = false;
|
||
287 + };
|
||
288 while (continueLoop) {
|
||
⋮
|
||
395 escPendingCancel = false;
|
||
392 - statusBar.setMode('input');
|
||
396 + finishRun();
|
||
397 console.log('已取消本次响应');
|
||
⋮
|
||
400 console.log(`错误: ${err.message || err}`);
|
||
401 + finishRun();
|
||
402 return;
|
||
⋮
|
||
415 showCursor();
|
||
411 - statusBar.setMode('input');
|
||
416 + finishRun();
|
||
417 console.log('已取消本次响应');
|
||
⋮
|
||
462 escPendingCancel = false;
|
||
458 - statusBar.setMode('input');
|
||
463 + finishRun();
|
||
464 console.log('工具被用户取消');
|
||
⋮
|
||
475 indicator.stop(finalLine);
|
||
471 - statusBar.setMode('input');
|
||
476 + finishRun();
|
||
477 console.log('工具被用户取消');
|
||
⋮
|
||
511 showCursor();
|
||
507 - if (statusBar) statusBar.setMode('input');
|
||
508 - isRunning = false;
|
||
512 + finishRun();
|
||
513 }
|
||
|
||
• Edited src/cli/index.js (+5 -2)
|
||
282 let localMessages = null;
|
||
283 + let userPersisted = false;
|
||
284 isRunning = true;
|
||
⋮
|
||
318 localMessages = buildApiMessages();
|
||
318 - if (userInput) {
|
||
319 + if (userInput && !userPersisted) {
|
||
320 localMessages.push({ role: 'user', content: userInput });
|
||
⋮
|
||
442 const userMsg = localMessages && localMessages[localMessages.length - 1];
|
||
442 - if (userMsg && userMsg.role === 'user') {
|
||
443 + if (userMsg && userMsg.role === 'user' && !userPersisted) {
|
||
444 state.messages.push(userMsg);
|
||
445 persistConversation();
|
||
446 + userPersisted = true;
|
||
447 }
|
||
⋮
|
||
499 persistConversation();
|
||
500 + localMessages = null;
|
||
501 continueLoop = true;
|
||
|
||
• Edited src/ui/status_bar.js (+18 -2)
|
||
18 let enabled = true;
|
||
19 + let scrollApplied = false;
|
||
20
|
||
⋮
|
||
24 const rows = process.stdout.rows || 24;
|
||
25 + if (rows < 2) return;
|
||
26 const left = mode === 'running' ? '按下Esc停止' : '输入/查看所有指令';
|
||
⋮
|
||
37 const line = `${displayLeft}${' '.repeat(Math.max(1, gap))}${right}`;
|
||
38 + const safeLine = line.slice(0, cols).padEnd(cols, ' ');
|
||
39
|
||
40 process.stdout.write('\x1b7'); // save cursor
|
||
41 + process.stdout.write(`\x1b[1;${rows - 1}r`); // reserve last line
|
||
42 + scrollApplied = true;
|
||
43 readline.cursorTo(process.stdout, 0, rows - 1);
|
||
44 readline.clearLine(process.stdout, 0);
|
||
40 - process.stdout.write(line.slice(0, cols));
|
||
45 + process.stdout.write(safeLine);
|
||
46 process.stdout.write('\x1b8'); // restore cursor
|
||
⋮
|
||
59
|
||
60 + const destroy = () => {
|
||
61 + if (!process.stdout.isTTY) return;
|
||
62 + if (scrollApplied) {
|
||
63 + process.stdout.write('\x1b[r'); // reset scroll region
|
||
64 + scrollApplied = false;
|
||
65 + }
|
||
66 + const rows = process.stdout.rows || 24;
|
||
67 + readline.cursorTo(process.stdout, 0, rows - 1);
|
||
68 + readline.clearLine(process.stdout, 0);
|
||
69 + };
|
||
70 +
|
||
71 if (process.stdout.isTTY) {
|
||
⋮
|
||
74
|
||
59 - return { render, setMode, setEnabled };
|
||
75 + return { render, setMode, setEnabled, destroy };
|
||
76 }
|
||
|
||
• Edited src/cli/index.js (+1 -0)
|
||
219 rl.on('close', () => {
|
||
220 + if (statusBar) statusBar.destroy();
|
||
221 process.stdout.write('\n');
|
||
|
||
• Edited src/ui/status_bar.js (+19 -5)
|
||
19 let scrollApplied = false;
|
||
20 + let lastRows = null;
|
||
21
|
||
⋮
|
||
25 const rows = process.stdout.rows || 24;
|
||
25 - if (rows < 2) return;
|
||
26 + if (rows < 3) return;
|
||
27 const left = mode === 'running' ? '按下Esc停止' : '输入/查看所有指令';
|
||
⋮
|
||
41 process.stdout.write('\x1b7'); // save cursor
|
||
41 - process.stdout.write(`\x1b[1;${rows - 1}r`); // reserve last line
|
||
42 - scrollApplied = true;
|
||
42 + if (!scrollApplied || lastRows !== rows) {
|
||
43 + process.stdout.write(`\x1b[1;${rows - 2}r`); // reserve last 2 lines
|
||
44 + scrollApplied = true;
|
||
45 + lastRows = rows;
|
||
46 + }
|
||
47 + // keep one blank line above status line
|
||
48 + readline.cursorTo(process.stdout, 0, rows - 2);
|
||
49 + readline.clearLine(process.stdout, 0);
|
||
50 + // render status line on last row
|
||
51 readline.cursorTo(process.stdout, 0, rows - 1);
|
||
⋮
|
||
74 const rows = process.stdout.rows || 24;
|
||
67 - readline.cursorTo(process.stdout, 0, rows - 1);
|
||
68 - readline.clearLine(process.stdout, 0);
|
||
75 + if (rows >= 2) {
|
||
76 + readline.cursorTo(process.stdout, 0, rows - 2);
|
||
77 + readline.clearLine(process.stdout, 0);
|
||
78 + }
|
||
79 + if (rows >= 1) {
|
||
80 + readline.cursorTo(process.stdout, 0, rows - 1);
|
||
81 + readline.clearLine(process.stdout, 0);
|
||
82 + }
|
||
83 };
|
||
|
||
• Edited src/ui/status_bar.js (+7 -17)
|
||
3 const readline = require('readline');
|
||
4 -
|
||
5 -function stripAnsi(text) {
|
||
6 - return String(text).replace(/\x1b\[[0-9;]*m/g, '');
|
||
7 -}
|
||
8 -
|
||
9 -function truncateToWidth(text, width) {
|
||
10 - if (width <= 0) return '';
|
||
11 - const plain = stripAnsi(text);
|
||
12 - if (plain.length <= width) return text;
|
||
13 - return plain.slice(0, Math.max(0, width - 1)) + '…';
|
||
14 -}
|
||
4 +const { visibleWidth, truncateVisible, padEndVisible } = require('../utils/text_width');
|
||
5
|
||
⋮
|
||
19 const right = `当前上下文token ${total}/${maxTokens}`;
|
||
30 - const leftPlain = stripAnsi(left);
|
||
31 - const rightPlain = stripAnsi(right);
|
||
32 - let gap = cols - leftPlain.length - rightPlain.length;
|
||
20 + const leftWidth = visibleWidth(left);
|
||
21 + const rightWidth = visibleWidth(right);
|
||
22 + let gap = cols - leftWidth - rightWidth;
|
||
23 let displayLeft = left;
|
||
24 if (gap < 1) {
|
||
35 - displayLeft = truncateToWidth(left, Math.max(1, cols - rightPlain.length - 1));
|
||
36 - gap = cols - stripAnsi(displayLeft).length - rightPlain.length;
|
||
25 + displayLeft = truncateVisible(left, Math.max(1, cols - rightWidth - 1));
|
||
26 + gap = cols - visibleWidth(displayLeft) - rightWidth;
|
||
27 }
|
||
28 const line = `${displayLeft}${' '.repeat(Math.max(1, gap))}${right}`;
|
||
39 - const safeLine = line.slice(0, cols).padEnd(cols, ' ');
|
||
29 + const safeLine = padEndVisible(truncateVisible(line, cols), cols);
|
||
30
|
||
|
||
• Edited src/ui/status_bar.js (+0 -7)
|
||
36 }
|
||
37 - // keep one blank line above status line
|
||
38 - readline.cursorTo(process.stdout, 0, rows - 2);
|
||
39 - readline.clearLine(process.stdout, 0);
|
||
37 // render status line on last row
|
||
⋮
|
||
61 const rows = process.stdout.rows || 24;
|
||
65 - if (rows >= 2) {
|
||
66 - readline.cursorTo(process.stdout, 0, rows - 2);
|
||
67 - readline.clearLine(process.stdout, 0);
|
||
68 - }
|
||
62 if (rows >= 1) {
|
||
|
||
• Edited src/ui/status_bar.js (+1 -1)
|
||
32 if (!scrollApplied || lastRows !== rows) {
|
||
33 - process.stdout.write(`\x1b[1;${rows - 2}r`); // reserve last 2 lines
|
||
33 + process.stdout.write(`\x1b[1;${rows - 1}r`); // reserve last line
|
||
34 scrollApplied = true;
|
||
|
||
• Edited src/ui/status_bar.js (+4 -1)
|
||
32 if (!scrollApplied || lastRows !== rows) {
|
||
33 - process.stdout.write(`\x1b[1;${rows - 1}r`); // reserve last line
|
||
33 + process.stdout.write(`\x1b[1;${rows - 2}r`); // reserve last 2 lines
|
||
34 scrollApplied = true;
|
||
⋮
|
||
36 }
|
||
37 + // keep one blank spacer line above status line
|
||
38 + readline.cursorTo(process.stdout, 0, rows - 2);
|
||
39 + readline.clearLine(process.stdout, 0);
|
||
40 // render status line on last row
|
||
|
||
• Edited src/ui/status_bar.js (+4 -0)
|
||
64 const rows = process.stdout.rows || 24;
|
||
65 + if (rows >= 2) {
|
||
66 + readline.cursorTo(process.stdout, 0, rows - 2);
|
||
67 + readline.clearLine(process.stdout, 0);
|
||
68 + }
|
||
69 if (rows >= 1) {
|
||
|
||
• Edited src/cli/index.js (+13 -0)
|
||
62
|
||
63 +function promptWithStatus(force = false) {
|
||
64 + if (!rl) return;
|
||
65 + if (statusBar) statusBar.render();
|
||
66 + if (process.stdout.isTTY) {
|
||
67 + const rows = process.stdout.rows || 24;
|
||
68 + if (rows >= 3) {
|
||
69 + readline.cursorTo(process.stdout, 0, rows - 3);
|
||
70 + readline.clearLine(process.stdout, 0);
|
||
71 + }
|
||
72 + }
|
||
73 + rl.prompt(force);
|
||
74 +}
|
||
75 +
|
||
76 readline.emitKeypressEvents(process.stdin);
|
||
|
||
• Edited src/cli/index.js (+7 -7)
|
||
153 }
|
||
154 - rl.prompt(true);
|
||
154 + promptWithStatus(true);
|
||
155 if (menuInjectedCommand) {
|
||
⋮
|
||
177 rl.setPrompt(PROMPT);
|
||
178 - rl.prompt();
|
||
178 + promptWithStatus();
|
||
179
|
||
⋮
|
||
187 escPendingCancel = false;
|
||
188 - rl.prompt();
|
||
188 + promptWithStatus();
|
||
189 return;
|
||
⋮
|
||
206 }
|
||
207 - rl.prompt();
|
||
207 + promptWithStatus();
|
||
208 return;
|
||
⋮
|
||
213 if (!input) {
|
||
214 - rl.prompt();
|
||
214 + promptWithStatus();
|
||
215 return;
|
||
⋮
|
||
220 if (result && result.exit) return;
|
||
221 - rl.prompt();
|
||
221 + promptWithStatus();
|
||
222 return;
|
||
⋮
|
||
228 await runAssistantLoop(line);
|
||
229 - rl.prompt();
|
||
229 + promptWithStatus();
|
||
230 });
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
67 const rows = process.stdout.rows || 24;
|
||
68 - if (rows >= 3) {
|
||
68 + if (rows >= 8) {
|
||
69 readline.cursorTo(process.stdout, 0, rows - 3);
|
||
|
||
• Edited src/cli/index.js (+1 -0)
|
||
49 });
|
||
50 +console.log('');
|
||
51
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
68 const rows = process.stdout.rows || 24;
|
||
69 - if (rows >= 8) {
|
||
69 + if (rows >= 9) {
|
||
70 readline.cursorTo(process.stdout, 0, rows - 3);
|
||
|
||
• Edited src/cli/index.js (+1 -0)
|
||
50 console.log('');
|
||
51 +console.log('');
|
||
52
|
||
|
||
• Edited src/cli/index.js (+1 -0)
|
||
302 if (statusBar) statusBar.setMode('input');
|
||
303 + if (statusBar) statusBar.render();
|
||
304 isRunning = false;
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
66 if (!rl) return;
|
||
67 - if (statusBar) statusBar.render();
|
||
67 if (process.stdout.isTTY) {
|
||
⋮
|
||
74 rl.prompt(force);
|
||
75 + if (statusBar) statusBar.render();
|
||
76 }
|
||
|
||
• Edited src/tools/edit_file.js (+1 -1)
|
||
60 const replacements = original.split(oldString).length - 1;
|
||
61 - fs.writeFileSync(target, updated, 'utf8');
|
||
61 + fs.writeFileSync(target, updated, { encoding: 'utf8', flag: 'r+' });
|
||
62 const diff = diffSummary(original, updated);
|
||
|
||
• Edited src/ui/status_bar.js (+1 -0)
|
||
32 if (!scrollApplied || lastRows !== rows) {
|
||
33 + process.stdout.write('\x1b[r'); // reset scroll region
|
||
34 process.stdout.write(`\x1b[1;${rows - 2}r`); // reserve last 2 lines
|
||
|
||
• Edited src/tools/edit_file.js (+1 -1)
|
||
60 const replacements = original.split(oldString).length - 1;
|
||
61 - fs.writeFileSync(target, updated, { encoding: 'utf8', flag: 'r+' });
|
||
61 + fs.writeFileSync(target, updated, 'utf8');
|
||
62 const diff = diffSummary(original, updated);
|
||
|
||
• Edited src/tools/edit_file.js (+7 -4)
|
||
47 try {
|
||
48 + let creating = false;
|
||
49 if (!fs.existsSync(target)) {
|
||
49 - return { success: false, error: '文件不存在' };
|
||
50 + creating = true;
|
||
51 + fs.mkdirSync(path.dirname(target), { recursive: true });
|
||
52 }
|
||
⋮
|
||
57 const original = fs.readFileSync(target, 'utf8');
|
||
56 - if (!original.includes(oldString)) {
|
||
58 + if (!creating && !original.includes(oldString)) {
|
||
59 return { success: false, error: 'old_string 未匹配到内容' };
|
||
60 }
|
||
59 - const updated = original.split(oldString).join(newString);
|
||
60 - const replacements = original.split(oldString).length - 1;
|
||
61 + const updated = creating ? newString : original.split(oldString).join(newString);
|
||
62 + let replacements = creating ? 0 : original.split(oldString).length - 1;
|
||
63 + if (creating && newString) replacements = 1;
|
||
64 fs.writeFileSync(target, updated, 'utf8');
|
||
|
||
• Edited src/tools/edit_file.js (+4 -0)
|
||
51 fs.mkdirSync(path.dirname(target), { recursive: true });
|
||
52 + fs.writeFileSync(target, '', 'utf8');
|
||
53 }
|
||
⋮
|
||
58 const original = fs.readFileSync(target, 'utf8');
|
||
59 + if (!creating && oldString === '') {
|
||
60 + return { success: false, error: 'old_string 不能为空,请从 read_file 内容中精确复制' };
|
||
61 + }
|
||
62 if (!creating && !original.includes(oldString)) {
|
||
|
||
• Edited src/cli/index.js (+5 -5)
|
||
330 if (!showThinkingLabel) return '';
|
||
331 - return { label: ' 思考中...', thinking: thinkingBuffer, colorThinking: true };
|
||
331 + return { label: '思考中...', thinking: thinkingBuffer, colorThinking: true };
|
||
332 });
|
||
⋮
|
||
380 if (state.thinkingMode) {
|
||
381 - spinner.stop(thinkingActive ? '○ 思考完成' : '○');
|
||
381 + spinner.stop(thinkingActive ? '∙ 思考完成' : '∙');
|
||
382 } else {
|
||
⋮
|
||
406 if (state.thinkingMode) {
|
||
407 - spinner.stop('○');
|
||
407 + spinner.stop('∙');
|
||
408 } else {
|
||
⋮
|
||
428 if (state.thinkingMode) {
|
||
429 - spinner.stop('○');
|
||
429 + spinner.stop('∙');
|
||
430 } else {
|
||
⋮
|
||
441 if (state.thinkingMode) {
|
||
442 - spinner.stop(thinkingActive ? '○ 思考完成' : '○');
|
||
442 + spinner.stop(thinkingActive ? '∙ 思考完成' : '∙');
|
||
443 } else {
|
||
|
||
• Edited src/cli/index.js (+3 -1)
|
||
229 userWriter.writeLine(`${cyan('用户:')}${line}`);
|
||
230 - await runAssistantLoop(line);
|
||
230 + state.messages.push({ role: 'user', content: line });
|
||
231 + persistConversation();
|
||
232 + await runAssistantLoop();
|
||
233 promptWithStatus();
|
||
|
||
• Edited src/cli/index.js (+2 -10)
|
||
294
|
||
295 -async function runAssistantLoop(userInput) {
|
||
295 +async function runAssistantLoop() {
|
||
296 let continueLoop = true;
|
||
⋮
|
||
299 const showCursor = () => process.stdout.write('\x1b[?25h');
|
||
300 - let localMessages = null;
|
||
301 - let userPersisted = false;
|
||
300 isRunning = true;
|
||
⋮
|
||
333
|
||
336 - if (!localMessages) {
|
||
337 - localMessages = buildApiMessages();
|
||
338 - if (userInput && !userPersisted) {
|
||
339 - localMessages.push({ role: 'user', content: userInput });
|
||
340 - }
|
||
341 - }
|
||
342 - const messages = localMessages;
|
||
334 + const messages = buildApiMessages();
|
||
335 try {
|
||
|
||
• Edited src/cli/index.js (+0 -7)
|
||
452
|
||
453 - const userMsg = localMessages && localMessages[localMessages.length - 1];
|
||
454 - if (userMsg && userMsg.role === 'user' && !userPersisted) {
|
||
455 - state.messages.push(userMsg);
|
||
456 - persistConversation();
|
||
457 - userPersisted = true;
|
||
458 - }
|
||
459 -
|
||
453 const toolCallList = Object.keys(toolCalls)
|
||
|
||
• Edited src/cli/index.js (+29 -16)
|
||
463 if (state.thinkingMode) assistantMsg.reasoning_content = fullThinkingBuffer || '';
|
||
464 - const toolMsgs = [];
|
||
464 + state.messages.push(assistantMsg);
|
||
465 + persistConversation();
|
||
466
|
||
467 for (const call of toolCallList) {
|
||
467 - if (escPendingCancel) {
|
||
468 - escPendingCancel = false;
|
||
469 - finishRun();
|
||
470 - console.log('工具被用户取消');
|
||
471 - return;
|
||
472 - }
|
||
468 let args = {};
|
||
⋮
|
||
472 const indicator = startToolDisplay(startLine);
|
||
478 - const toolResult = await executeTool({ workspace: WORKSPACE, config, allowMode: state.allowMode, toolCall: call });
|
||
473 if (escPendingCancel) {
|
||
⋮
|
||
475 indicator.stop(finalLine);
|
||
476 + const cancelledRaw = { success: false, error: '任务被用户取消' };
|
||
477 + const resultLines = formatResultLines(call.function.name, args, cancelledRaw);
|
||
478 + printResultLines(resultLines);
|
||
479 + state.messages.push({
|
||
480 + role: 'tool',
|
||
481 + tool_call_id: call.id,
|
||
482 + content: '任务被用户取消',
|
||
483 + tool_raw: cancelledRaw,
|
||
484 + });
|
||
485 + persistConversation();
|
||
486 finishRun();
|
||
483 - console.log('工具被用户取消');
|
||
487 return;
|
||
488 }
|
||
489 + let toolResult = await executeTool({ workspace: WORKSPACE, config, allowMode: state.allowMode, toolCall: call });
|
||
490 + if (escPendingCancel) {
|
||
491 + escPendingCancel = false;
|
||
492 + toolResult = {
|
||
493 + success: false,
|
||
494 + tool: call.function.name,
|
||
495 + error: '任务被用户取消',
|
||
496 + raw: { success: false, error: '任务被用户取消' },
|
||
497 + formatted: '任务被用户取消',
|
||
498 + };
|
||
499 + }
|
||
500 indicator.stop(finalLine);
|
||
⋮
|
||
504
|
||
491 - toolMsgs.push({
|
||
505 + state.messages.push({
|
||
506 role: 'tool',
|
||
⋮
|
||
510 });
|
||
511 + persistConversation();
|
||
512 + if (toolResult.raw && toolResult.raw.success === false && toolResult.error === '任务被用户取消') {
|
||
513 + finishRun();
|
||
514 + return;
|
||
515 + }
|
||
516 }
|
||
517
|
||
499 - state.messages.push(assistantMsg);
|
||
500 - for (const toolMsg of toolMsgs) {
|
||
501 - state.messages.push(toolMsg);
|
||
502 - }
|
||
503 - persistConversation();
|
||
504 - localMessages = null;
|
||
518 continueLoop = true;
|
||
|
||
• Edited src/ui/tool_display.js (+1 -0)
|
||
132 const msg = raw && raw.error ? raw.error : '执行失败';
|
||
133 + if (msg === '任务被用户取消') return [red(msg)];
|
||
134 return [red(`失败: ${msg}`)];
|
||
|
||
• Edited src/tools/run_command.js (+18 -2)
|
||
11
|
||
12 -function runCommandTool(workspace, args) {
|
||
12 +function runCommandTool(workspace, args, abortSignal) {
|
||
13 return new Promise((resolve) => {
|
||
⋮
|
||
17 if (!cmd) return resolve({ success: false, error: 'command 不能为空' });
|
||
18 + if (abortSignal && abortSignal.aborted) {
|
||
19 + return resolve({ success: false, error: '任务被用户取消', cancelled: true });
|
||
20 + }
|
||
21 const timeoutMs = Math.max(0, timeoutSec) * 1000;
|
||
19 - exec(cmd, { cwd, timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024, shell: true }, (err, stdout, stderr) => {
|
||
22 + let finished = false;
|
||
23 + const child = exec(cmd, { cwd, timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024, shell: true }, (err, stdout, stderr) => {
|
||
24 + if (finished) return;
|
||
25 + finished = true;
|
||
26 + if (abortSignal) abortSignal.removeEventListener('abort', onAbort);
|
||
27 const output = [stdout, stderr].filter(Boolean).join('');
|
||
⋮
|
||
40 });
|
||
41 + const onAbort = () => {
|
||
42 + if (finished) return;
|
||
43 + finished = true;
|
||
44 + try {
|
||
45 + child.kill('SIGTERM');
|
||
46 + } catch (_) {}
|
||
47 + return resolve({ success: false, error: '任务被用户取消', cancelled: true });
|
||
48 + };
|
||
49 + if (abortSignal) abortSignal.addEventListener('abort', onAbort, { once: true });
|
||
50 });
|
||
|
||
• Edited src/tools/web_search.js (+16 -2)
|
||
2
|
||
3 -async function webSearchTool(config, args) {
|
||
3 +async function webSearchTool(config, args, abortSignal) {
|
||
4 + if (abortSignal && abortSignal.aborted) {
|
||
5 + return { success: false, error: '任务被用户取消', cancelled: true };
|
||
6 + }
|
||
7 const body = {
|
||
⋮
|
||
23 body: JSON.stringify(body),
|
||
24 + signal: abortSignal,
|
||
25 });
|
||
⋮
|
||
31 } catch (err) {
|
||
32 + if (err && err.name === 'AbortError') {
|
||
33 + return { success: false, error: '任务被用户取消', cancelled: true };
|
||
34 + }
|
||
35 return { success: false, error: err.message || String(err) };
|
||
⋮
|
||
38
|
||
32 -async function extractWebpageTool(config, args, savePath) {
|
||
39 +async function extractWebpageTool(config, args, savePath, abortSignal) {
|
||
40 + if (abortSignal && abortSignal.aborted) {
|
||
41 + return { success: false, error: '任务被用户取消', cancelled: true };
|
||
42 + }
|
||
43 const body = {
|
||
⋮
|
||
52 body: JSON.stringify(body),
|
||
53 + signal: abortSignal,
|
||
54 });
|
||
⋮
|
||
66 } catch (err) {
|
||
67 + if (err && err.name === 'AbortError') {
|
||
68 + return { success: false, error: '任务被用户取消', cancelled: true };
|
||
69 + }
|
||
70 return { success: false, error: err.message || String(err) };
|
||
|
||
• Edited src/tools/dispatcher.js (+15 -5)
|
||
140
|
||
141 -async function executeTool({ workspace, config, allowMode, toolCall }) {
|
||
141 +async function executeTool({ workspace, config, allowMode, toolCall, abortSignal }) {
|
||
142 const name = toolCall.function.name;
|
||
⋮
|
||
164
|
||
165 + if (abortSignal && abortSignal.aborted) {
|
||
166 + return {
|
||
167 + success: false,
|
||
168 + tool: name,
|
||
169 + error: '任务被用户取消',
|
||
170 + formatted: '任务被用户取消',
|
||
171 + raw: { success: false, error: '任务被用户取消', cancelled: true },
|
||
172 + };
|
||
173 + }
|
||
174 +
|
||
175 let raw;
|
||
⋮
|
||
177 else if (name === 'edit_file') raw = editFileTool(workspace, args);
|
||
168 - else if (name === 'run_command') raw = await runCommandTool(workspace, args);
|
||
169 - else if (name === 'web_search') raw = await webSearchTool(config, args);
|
||
178 + else if (name === 'run_command') raw = await runCommandTool(workspace, args, abortSignal);
|
||
179 + else if (name === 'web_search') raw = await webSearchTool(config, args, abortSignal);
|
||
180 else if (name === 'extract_webpage') {
|
||
⋮
|
||
186 } else {
|
||
177 - raw = await extractWebpageTool(config, args, targetPath);
|
||
187 + raw = await extractWebpageTool(config, args, targetPath, abortSignal);
|
||
188 }
|
||
189 } else {
|
||
180 - raw = await extractWebpageTool(config, args, targetPath);
|
||
190 + raw = await extractWebpageTool(config, args, targetPath, abortSignal);
|
||
191 }
|
||
|
||
• Edited src/cli/index.js (+4 -0)
|
||
63 let isRunning = false;
|
||
64 +let activeToolController = null;
|
||
65
|
||
⋮
|
||
103 }
|
||
104 + if (activeToolController && typeof activeToolController.abort === 'function') {
|
||
105 + activeToolController.abort();
|
||
106 + }
|
||
107 return;
|
||
|
||
• Edited src/cli/index.js (+11 -3)
|
||
479 indicator.stop(finalLine);
|
||
480 - const cancelledRaw = { success: false, error: '任务被用户取消' };
|
||
480 + const cancelledRaw = { success: false, error: '任务被用户取消', cancelled: true };
|
||
481 const resultLines = formatResultLines(call.function.name, args, cancelledRaw);
|
||
⋮
|
||
492 }
|
||
493 - let toolResult = await executeTool({ workspace: WORKSPACE, config, allowMode: state.allowMode, toolCall: call });
|
||
493 + activeToolController = new AbortController();
|
||
494 + let toolResult = await executeTool({
|
||
495 + workspace: WORKSPACE,
|
||
496 + config,
|
||
497 + allowMode: state.allowMode,
|
||
498 + toolCall: call,
|
||
499 + abortSignal: activeToolController.signal,
|
||
500 + });
|
||
501 + activeToolController = null;
|
||
502 if (escPendingCancel) {
|
||
⋮
|
||
507 error: '任务被用户取消',
|
||
500 - raw: { success: false, error: '任务被用户取消' },
|
||
508 + raw: { success: false, error: '任务被用户取消', cancelled: true },
|
||
509 formatted: '任务被用户取消',
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
523 persistConversation();
|
||
524 - if (toolResult.raw && toolResult.raw.success === false && toolResult.error === '任务被用户取消') {
|
||
524 + if (toolResult.raw && toolResult.raw.success === false && toolResult.raw.error === '任务被用户取消') {
|
||
525 finishRun();
|
||
|
||
• Edited src/ui/tool_display.js (+1 -0)
|
||
219 });
|
||
220 + process.stdout.write('\n');
|
||
221 }
|
||
|
||
• Edited src/cli/index.js (+4 -6)
|
||
403 clearTimeout(thinkingDelay);
|
||
404 - if (state.thinkingMode) {
|
||
404 + if (escPendingCancel) {
|
||
405 + spinner.stopSilent();
|
||
406 + } else if (state.thinkingMode) {
|
||
407 spinner.stop('∙');
|
||
⋮
|
||
427 clearTimeout(thinkingDelay);
|
||
426 - if (state.thinkingMode) {
|
||
427 - spinner.stop('∙');
|
||
428 - } else {
|
||
429 - spinner.stopSilent();
|
||
430 - }
|
||
428 + spinner.stopSilent();
|
||
429 showCursor();
|
||
|
||
• Edited src/cli/index.js (+8 -3)
|
||
20 const { applyUsage, normalizeTokenUsage } = require('../utils/token_usage');
|
||
21 -const { gray, cyan, green } = require('../utils/colors');
|
||
21 +const { gray, cyan, green, red } = require('../utils/colors');
|
||
22 const { createIndentedWriter } = require('../ui/indented_writer');
|
||
⋮
|
||
78
|
||
79 +function printCancelLine() {
|
||
80 + console.log('');
|
||
81 + process.stdout.write(` └ ${red('已取消本次响应')}\n\n`);
|
||
82 +}
|
||
83 +
|
||
84 readline.emitKeypressEvents(process.stdin);
|
||
⋮
|
||
419 finishRun();
|
||
415 - console.log('已取消本次响应');
|
||
420 + printCancelLine();
|
||
421 return;
|
||
⋮
|
||
435 finishRun();
|
||
431 - console.log('已取消本次响应');
|
||
436 + printCancelLine();
|
||
437 return;
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
80 console.log('');
|
||
81 - process.stdout.write(` └ ${red('已取消本次响应')}\n\n`);
|
||
81 + process.stdout.write(` ${red('已取消本次响应')}\n\n`);
|
||
82 }
|
||
|
||
• Edited src/ui/status_bar.js (+7 -1)
|
||
18 const total = typeof getTokens === 'function' ? getTokens() : 0;
|
||
19 - const right = `当前上下文token ${total}/${maxTokens}`;
|
||
19 + const formatCount = (value) => {
|
||
20 + const num = Number(value) || 0;
|
||
21 + if (num < 1000) return String(num);
|
||
22 + const k = Math.round((num / 1000) * 10) / 10;
|
||
23 + return `${k % 1 === 0 ? k.toFixed(0) : k.toFixed(1)}k`;
|
||
24 + };
|
||
25 + const right = `当前上下文 ${formatCount(total)}/${maxTokens}`;
|
||
26 const leftWidth = visibleWidth(left);
|
||
|
||
• Edited prompts/system.txt (+1 -0)
|
||
5 - 当前时间:{current_time}
|
||
6 +- 当前模型:{model_id}
|
||
7 - 工作区路径:{path}
|
||
|
||
• Edited src/core/context.js (+1 -0)
|
||
50 git: getGitInfo(opts.workspace),
|
||
51 + model_id: opts.modelId || '',
|
||
52 };
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
277 function buildApiMessages() {
|
||
278 - const system = buildSystemPrompt(systemPrompt, { workspace: WORKSPACE, allowMode: state.allowMode });
|
||
278 + const system = buildSystemPrompt(systemPrompt, { workspace: WORKSPACE, allowMode: state.allowMode, modelId: state.modelId
|
||
|| state.modelKey });
|
||
279 const messages = [{ role: 'system', content: system }];
|
||
|
||
• Edited src/core/context.js (+1 -1)
|
||
39 const allowModeValue = opts.allowMode === 'read_only'
|
||
40 - ? `${opts.allowMode}\n 已禁用 edit_file、run_command。若用户要求使用,请告知当前无权限需要请求切换权限。`
|
||
40 + ? `${opts.allowMode}\n 已禁用 edit_file、run_command。若用户要求使用,请告知当前无权限需要用户输入 /allow 切换权限。`
|
||
41 : opts.allowMode;
|
||
|
||
• Edited src/tools/dispatcher.js (+1 -1)
|
||
155 if (allowMode === 'read_only' && (name === 'edit_file' || name === 'run_command')) {
|
||
156 - const note = '当前为只读模式,已禁用 edit_file、run_command。若需使用,请先请求切换权限。';
|
||
156 + const note = '当前为只读模式,已禁用 edit_file、run_command。若需使用,请告知当前无权限需要用户输入 /allow 切换权限。';
|
||
157 return {
|
||
|
||
• Edited src/storage/conversation_store.js (+12 -3)
|
||
36 for (const msg of messages) {
|
||
37 - if (msg.role === 'user' && typeof msg.content === 'string' && msg.content.trim()) {
|
||
38 - const content = msg.content.trim();
|
||
39 - return content.length > 50 ? `${content.slice(0, 50)}...` : content;
|
||
37 + if (msg.role === 'user') {
|
||
38 + let content = '';
|
||
39 + if (typeof msg.content === 'string') {
|
||
40 + content = msg.content.trim();
|
||
41 + } else if (Array.isArray(msg.content)) {
|
||
42 + content = msg.content
|
||
43 + .filter((part) => part && part.type === 'text' && typeof part.text === 'string')
|
||
44 + .map((part) => part.text)
|
||
45 + .join(' ')
|
||
46 + .trim();
|
||
47 + }
|
||
48 + if (content) return content.length > 50 ? `${content.slice(0, 50)}...` : content;
|
||
49 }
|
||
|
||
• Edited src/cli/index.js (+132 -1)
|
||
20 const { applyUsage, normalizeTokenUsage } = require('../utils/token_usage');
|
||
21 -const { gray, cyan, green, red } = require('../utils/colors');
|
||
21 +const { gray, cyan, green, red, blue } = require('../utils/colors');
|
||
22 const { createIndentedWriter } = require('../ui/indented_writer');
|
||
23 const { createStatusBar } = require('../ui/status_bar');
|
||
24 +const { visibleWidth } = require('../utils/text_width');
|
||
25 +const { readMediafileTool } = require('../tools/read_mediafile');
|
||
26
|
||
⋮
|
||
66 let activeToolController = null;
|
||
67 +let currentMedia = { tokens: [], text: '' };
|
||
68 +
|
||
69 +const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.tiff', '.heic']);
|
||
70 +const VIDEO_EXTS = new Set(['.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v']);
|
||
71 +
|
||
72 +function getPathExt(p) {
|
||
73 + const idx = p.lastIndexOf('.');
|
||
74 + if (idx === -1) return '';
|
||
75 + return p.slice(idx).toLowerCase();
|
||
76 +}
|
||
77 +
|
||
78 +function decodeEscapedPath(p) {
|
||
79 + return p.replace(/\\ /g, ' ').replace(/\\\\/g, '\\');
|
||
80 +}
|
||
81 +
|
||
82 +function findMediaMatches(line) {
|
||
83 + const matches = [];
|
||
84 + const quoted = /'([^']+)'/g;
|
||
85 + let m;
|
||
86 + while ((m = quoted.exec(line)) !== null) {
|
||
87 + matches.push({ raw: m[0], path: m[1], index: m.index });
|
||
88 + }
|
||
89 + const extGroup = Array.from(new Set([...IMAGE_EXTS, ...VIDEO_EXTS]))
|
||
90 + .map((e) => e.replace('.', '\\.'))
|
||
91 + .join('|');
|
||
92 + const unquoted = new RegExp(`/((?:\\\\ |[^\\s])+?)\\.(${extGroup})`, 'g');
|
||
93 + while ((m = unquoted.exec(line)) !== null) {
|
||
94 + const raw = `/${m[1]}.${m[2]}`;
|
||
95 + matches.push({ raw, path: raw, index: m.index });
|
||
96 + }
|
||
97 + matches.sort((a, b) => a.index - b.index);
|
||
98 + return matches;
|
||
99 +}
|
||
100 +
|
||
101 +function applyMediaTokens(line) {
|
||
102 + if (!line) return { line: '', tokens: [] };
|
||
103 + const matches = findMediaMatches(line);
|
||
104 + if (!matches.length) return { line, tokens: [] };
|
||
105 +
|
||
106 + let images = 0;
|
||
107 + let videos = 0;
|
||
108 + let cursor = 0;
|
||
109 + let out = '';
|
||
110 + const tokens = [];
|
||
111 +
|
||
112 + for (const match of matches) {
|
||
113 + if (match.index < cursor) continue;
|
||
114 + const before = line.slice(cursor, match.index);
|
||
115 + out += before;
|
||
116 + const decoded = decodeEscapedPath(match.path);
|
||
117 + const ext = getPathExt(decoded);
|
||
118 + const isImage = IMAGE_EXTS.has(ext);
|
||
119 + const isVideo = VIDEO_EXTS.has(ext);
|
||
120 + if (!isImage && !isVideo) {
|
||
121 + out += match.raw;
|
||
122 + cursor = match.index + match.raw.length;
|
||
123 + continue;
|
||
124 + }
|
||
125 + if (isImage && images >= 9) {
|
||
126 + out += match.raw;
|
||
127 + cursor = match.index + match.raw.length;
|
||
128 + continue;
|
||
129 + }
|
||
130 + if (isVideo && videos >= 1) {
|
||
131 + out += match.raw;
|
||
132 + cursor = match.index + match.raw.length;
|
||
133 + continue;
|
||
134 + }
|
||
135 + const token = isImage ? `[图片 #${images + 1}]` : `[视频 #${videos + 1}]`;
|
||
136 + tokens.push({ token, path: decoded, type: isImage ? 'image' : 'video' });
|
||
137 + out += token;
|
||
138 + if (isImage) images += 1;
|
||
139 + if (isVideo) videos += 1;
|
||
140 + cursor = match.index + match.raw.length;
|
||
141 + }
|
||
142 + out += line.slice(cursor);
|
||
143 + return { line: out, tokens };
|
||
144 +}
|
||
145 +
|
||
146 +function colorizeTokens(line) {
|
||
147 + return line.replace(/\[(图片|视频) #\d+\]/g, (t) => blue(t));
|
||
148 +}
|
||
149 +
|
||
150 +function refreshInputLine() {
|
||
151 + if (!rl || !process.stdout.isTTY || commandMenuActive) return;
|
||
152 + const line = rl.line || '';
|
||
153 + const colorLine = colorizeTokens(line);
|
||
154 + readline.clearLine(process.stdout, 0);
|
||
155 + readline.cursorTo(process.stdout, 0);
|
||
156 + process.stdout.write(PROMPT + colorLine);
|
||
157 + const cursorCol = visibleWidth(PROMPT) + visibleWidth(line.slice(0, rl.cursor || 0));
|
||
158 + readline.cursorTo(process.stdout, cursorCol);
|
||
159 +}
|
||
160 +
|
||
161 +function buildUserContent(line, tokens) {
|
||
162 + if (!tokens.length) return line;
|
||
163 + const parts = [];
|
||
164 + const tokenRe = /\[(图片|视频) #\d+\]/g;
|
||
165 + let last = 0;
|
||
166 + let m;
|
||
167 + while ((m = tokenRe.exec(line)) !== null) {
|
||
168 + if (m.index > last) {
|
||
169 + const text = line.slice(last, m.index);
|
||
170 + if (text) parts.push({ type: 'text', text });
|
||
171 + }
|
||
172 + const token = m[0];
|
||
173 + const info = tokens.find((t) => t.token === token);
|
||
174 + if (info) {
|
||
175 + const media = readMediafileTool(WORKSPACE, { path: info.path });
|
||
176 + if (media && media.success) {
|
||
177 + const url = `data:${media.mime};base64,${media.b64}`;
|
||
178 + parts.push({
|
||
179 + type: media.type === 'image' ? 'image_url' : 'video_url',
|
||
180 + [media.type === 'image' ? 'image_url' : 'video_url']: { url },
|
||
181 + });
|
||
182 + } else {
|
||
183 + parts.push({ type: 'text', text: token });
|
||
184 + }
|
||
185 + } else {
|
||
186 + parts.push({ type: 'text', text: token });
|
||
187 + }
|
||
188 + last = tokenRe.lastIndex;
|
||
189 + }
|
||
190 + if (last < line.length) {
|
||
191 + const tail = line.slice(last);
|
||
192 + if (tail) parts.push({ type: 'text', text: tail });
|
||
193 + }
|
||
194 + return parts;
|
||
195 +}
|
||
196
|
||
|
||
• Edited src/cli/index.js (+11 -0)
|
||
244 }
|
||
245 + if (!isRunning) {
|
||
246 + const applied = applyMediaTokens(rl.line || '');
|
||
247 + if (applied.line !== (rl.line || '')) {
|
||
248 + rl.line = applied.line;
|
||
249 + rl.cursor = rl.line.length;
|
||
250 + currentMedia = { tokens: applied.tokens, text: applied.line };
|
||
251 + } else if (currentMedia.text !== applied.line) {
|
||
252 + currentMedia = { tokens: applied.tokens, text: applied.line };
|
||
253 + }
|
||
254 + refreshInputLine();
|
||
255 + }
|
||
256 if (statusBar) statusBar.render();
|
||
|
||
• Edited src/cli/index.js (+8 -3)
|
||
346 }
|
||
347 - const input = line.trim();
|
||
347 + const applied = applyMediaTokens(line);
|
||
348 + const normalizedLine = applied.line;
|
||
349 + currentMedia = { tokens: applied.tokens, text: normalizedLine };
|
||
350 + const input = normalizedLine.trim();
|
||
351 if (menuJustClosedAt) {
|
||
⋮
|
||
382 const userWriter = createIndentedWriter(' ');
|
||
380 - userWriter.writeLine(`${cyan('用户:')}${line}`);
|
||
381 - state.messages.push({ role: 'user', content: line });
|
||
383 + const displayLine = colorizeTokens(normalizedLine);
|
||
384 + userWriter.writeLine(`${cyan('用户:')}${displayLine}`);
|
||
385 + const content = buildUserContent(normalizedLine, currentMedia.tokens);
|
||
386 + state.messages.push({ role: 'user', content });
|
||
387 persistConversation();
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
89 const extGroup = Array.from(new Set([...IMAGE_EXTS, ...VIDEO_EXTS]))
|
||
90 - .map((e) => e.replace('.', '\\.'))
|
||
90 + .map((e) => e.slice(1))
|
||
91 .join('|');
|
||
|
||
• Edited src/cli/index.js (+7 -0)
|
||
31 const MENU_PAGE_SIZE = 6;
|
||
32 +const SLASH_COMMANDS = new Set(['/new', '/resume', '/allow', '/model', '/status', '/compact', '/config', '/help', '/exit']);
|
||
33
|
||
⋮
|
||
197
|
||
198 +function isSlashCommand(line) {
|
||
199 + if (!line || line[0] !== '/') return false;
|
||
200 + const cmd = line.split(/\s+/)[0];
|
||
201 + return SLASH_COMMANDS.has(cmd);
|
||
202 +}
|
||
203 +
|
||
204 function promptWithStatus(force = false) {
|
||
|
||
• Edited src/cli/index.js (+2 -1)
|
||
252 if (!isRunning) {
|
||
253 - const applied = applyMediaTokens(rl.line || '');
|
||
253 + const rawLine = rl.line || '';
|
||
254 + const applied = isSlashCommand(rawLine) ? { line: rawLine, tokens: [] } : applyMediaTokens(rawLine);
|
||
255 if (applied.line !== (rl.line || '')) {
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
354 }
|
||
355 - const applied = applyMediaTokens(line);
|
||
355 + const applied = isSlashCommand(line) ? { line, tokens: [] } : applyMediaTokens(line);
|
||
356 const normalizedLine = applied.line;
|
||
|
||
• Edited src/cli/index.js (+5 -0)
|
||
117 const decoded = decodeEscapedPath(match.path);
|
||
118 + if (!fs.existsSync(decoded)) {
|
||
119 + out += match.raw;
|
||
120 + cursor = match.index + match.raw.length;
|
||
121 + continue;
|
||
122 + }
|
||
123 const ext = getPathExt(decoded);
|
||
|
||
• Edited src/cli/index.js (+92 -52)
|
||
68 let currentMedia = { tokens: [], text: '' };
|
||
69 +let pendingSlashTimer = null;
|
||
70
|
||
⋮
|
||
167
|
||
168 +function removeTokenAtCursor(line, cursor) {
|
||
169 + const tokenRe = /\[(图片|视频) #\d+\]/g;
|
||
170 + let m;
|
||
171 + while ((m = tokenRe.exec(line)) !== null) {
|
||
172 + const start = m.index;
|
||
173 + const end = m.index + m[0].length;
|
||
174 + if (cursor > start && cursor <= end) {
|
||
175 + const nextLine = line.slice(0, start) + line.slice(end);
|
||
176 + return { line: nextLine, cursor: start, removed: true };
|
||
177 + }
|
||
178 + }
|
||
179 + return { line, cursor, removed: false };
|
||
180 +}
|
||
181 +
|
||
182 function buildUserContent(line, tokens) {
|
||
⋮
|
||
261 }
|
||
262 + if (!isRunning && key && key.name === 'backspace') {
|
||
263 + const line = rl.line || '';
|
||
264 + const cursor = rl.cursor || 0;
|
||
265 + const updated = removeTokenAtCursor(line, cursor);
|
||
266 + if (updated.removed) {
|
||
267 + rl.line = updated.line;
|
||
268 + rl.cursor = updated.cursor;
|
||
269 + refreshInputLine();
|
||
270 + return;
|
||
271 + }
|
||
272 + }
|
||
273 if (key && key.name === 'escape' && isRunning) {
|
||
⋮
|
||
282 }
|
||
283 + if (pendingSlashTimer && (str !== '/' || (rl.line && rl.line !== '/'))) {
|
||
284 + clearTimeout(pendingSlashTimer);
|
||
285 + pendingSlashTimer = null;
|
||
286 + }
|
||
287 if (!isRunning) {
|
||
⋮
|
||
300 if (str === '/' && (rl.line === '' || rl.line === '/')) {
|
||
271 - commandMenuActive = true;
|
||
272 - if (rl) {
|
||
273 - rl.pause();
|
||
274 - rl.line = '';
|
||
275 - rl.cursor = 0;
|
||
276 - readline.clearLine(process.stdout, 0);
|
||
277 - readline.cursorTo(process.stdout, 0);
|
||
301 + if (pendingSlashTimer) {
|
||
302 + clearTimeout(pendingSlashTimer);
|
||
303 + pendingSlashTimer = null;
|
||
304 }
|
||
279 - menuSearchTerm = '';
|
||
280 - menuAbortController = new AbortController();
|
||
281 - void openCommandMenu({
|
||
282 - rl,
|
||
283 - prompt: PROMPT,
|
||
284 - pageSize: MENU_PAGE_SIZE,
|
||
285 - colorEnabled: process.stdout.isTTY,
|
||
286 - resetAnsi: '\x1b[0m',
|
||
287 - onInput: (input) => {
|
||
288 - menuSearchTerm = input || '';
|
||
289 - },
|
||
290 - abortSignal: menuAbortController.signal,
|
||
291 - })
|
||
292 - .then((result) => {
|
||
293 - if (result && result.chosen && !result.cancelled) {
|
||
294 - menuInjectedCommand = result.chosen;
|
||
295 - }
|
||
296 - })
|
||
297 - .finally(() => {
|
||
298 - commandMenuActive = false;
|
||
299 - menuAbortController = null;
|
||
300 - menuJustClosedAt = menuInjectedCommand ? Date.now() : 0;
|
||
301 - menuLastSearchTerm = menuSearchTerm;
|
||
302 - drainStdin();
|
||
303 - rl.line = '';
|
||
304 - rl.cursor = 0;
|
||
305 - menuSearchTerm = '';
|
||
306 - if (process.stdout.isTTY) {
|
||
307 - readline.clearScreenDown(process.stdout);
|
||
308 - }
|
||
309 - // Clear possible echoes from the base readline line (current + previous line).
|
||
310 - if (process.stdout.isTTY) {
|
||
311 - readline.clearLine(process.stdout, 0);
|
||
312 - readline.cursorTo(process.stdout, 0);
|
||
313 - readline.moveCursor(process.stdout, 0, -1);
|
||
314 - readline.clearLine(process.stdout, 0);
|
||
315 - readline.cursorTo(process.stdout, 0);
|
||
316 - } else {
|
||
305 + pendingSlashTimer = setTimeout(() => {
|
||
306 + pendingSlashTimer = null;
|
||
307 + if (rl.line === '' || rl.line === '/') {
|
||
308 + commandMenuActive = true;
|
||
309 + if (rl) {
|
||
310 + rl.pause();
|
||
311 + rl.line = '';
|
||
312 + rl.cursor = 0;
|
||
313 readline.clearLine(process.stdout, 0);
|
||
⋮
|
||
315 }
|
||
320 - promptWithStatus(true);
|
||
321 - if (menuInjectedCommand) {
|
||
322 - const injected = menuInjectedCommand;
|
||
323 - menuInjectedCommand = null;
|
||
324 - setImmediate(() => injectLine(injected));
|
||
325 - }
|
||
326 - });
|
||
316 + menuSearchTerm = '';
|
||
317 + menuAbortController = new AbortController();
|
||
318 + void openCommandMenu({
|
||
319 + rl,
|
||
320 + prompt: PROMPT,
|
||
321 + pageSize: MENU_PAGE_SIZE,
|
||
322 + colorEnabled: process.stdout.isTTY,
|
||
323 + resetAnsi: '\x1b[0m',
|
||
324 + onInput: (input) => {
|
||
325 + menuSearchTerm = input || '';
|
||
326 + },
|
||
327 + abortSignal: menuAbortController.signal,
|
||
328 + })
|
||
329 + .then((result) => {
|
||
330 + if (result && result.chosen && !result.cancelled) {
|
||
331 + menuInjectedCommand = result.chosen;
|
||
332 + }
|
||
333 + })
|
||
334 + .finally(() => {
|
||
335 + commandMenuActive = false;
|
||
336 + menuAbortController = null;
|
||
337 + menuJustClosedAt = menuInjectedCommand ? Date.now() : 0;
|
||
338 + menuLastSearchTerm = menuSearchTerm;
|
||
339 + drainStdin();
|
||
340 + rl.line = '';
|
||
341 + rl.cursor = 0;
|
||
342 + menuSearchTerm = '';
|
||
343 + if (process.stdout.isTTY) {
|
||
344 + readline.clearScreenDown(process.stdout);
|
||
345 + }
|
||
346 + // Clear possible echoes from the base readline line (current + previous line).
|
||
347 + if (process.stdout.isTTY) {
|
||
348 + readline.clearLine(process.stdout, 0);
|
||
349 + readline.cursorTo(process.stdout, 0);
|
||
350 + readline.moveCursor(process.stdout, 0, -1);
|
||
351 + readline.clearLine(process.stdout, 0);
|
||
352 + readline.cursorTo(process.stdout, 0);
|
||
353 + } else {
|
||
354 + readline.clearLine(process.stdout, 0);
|
||
355 + readline.cursorTo(process.stdout, 0);
|
||
356 + }
|
||
357 + promptWithStatus(true);
|
||
358 + if (menuInjectedCommand) {
|
||
359 + const injected = menuInjectedCommand;
|
||
360 + menuInjectedCommand = null;
|
||
361 + setImmediate(() => injectLine(injected));
|
||
362 + }
|
||
363 + });
|
||
364 + }
|
||
365 + }, 80);
|
||
366 + return;
|
||
367 }
|
||
|
||
• Edited src/cli/index.js (+9 -1)
|
||
399 }
|
||
400 - const applied = isSlashCommand(line) ? { line, tokens: [] } : applyMediaTokens(line);
|
||
400 + let applied = { line, tokens: [] };
|
||
401 + if (!isSlashCommand(line)) {
|
||
402 + const hasToken = /\[(图片|视频) #\d+\]/.test(line);
|
||
403 + if (hasToken && currentMedia.tokens.length && currentMedia.text === line) {
|
||
404 + applied = { line, tokens: currentMedia.tokens };
|
||
405 + } else {
|
||
406 + applied = applyMediaTokens(line);
|
||
407 + }
|
||
408 + }
|
||
409 const normalizedLine = applied.line;
|
||
|
||
• Edited src/cli/index.js (+8 -1)
|
||
80 function decodeEscapedPath(p) {
|
||
81 - return p.replace(/\\ /g, ' ').replace(/\\\\/g, '\\');
|
||
81 + let text = String(p || '');
|
||
82 + if (text.startsWith('file://')) {
|
||
83 + text = text.replace(/^file:\/\//, '');
|
||
84 + }
|
||
85 + try {
|
||
86 + text = decodeURIComponent(text);
|
||
87 + } catch (_) {}
|
||
88 + return text.replace(/\\ /g, ' ').replace(/\\\\/g, '\\');
|
||
89 }
|
||
|
||
• Edited src/cli/index.js (+0 -0)
|
||
|
||
• Edited src/cli/index.js (+9 -29)
|
||
190 if (!tokens.length) return line;
|
||
191 - const parts = [];
|
||
192 - const tokenRe = /\[(图片|视频) #\d+\]/g;
|
||
193 - let last = 0;
|
||
194 - let m;
|
||
195 - while ((m = tokenRe.exec(line)) !== null) {
|
||
196 - if (m.index > last) {
|
||
197 - const text = line.slice(last, m.index);
|
||
198 - if (text) parts.push({ type: 'text', text });
|
||
199 - }
|
||
200 - const token = m[0];
|
||
201 - const info = tokens.find((t) => t.token === token);
|
||
202 - if (info) {
|
||
203 - const media = readMediafileTool(WORKSPACE, { path: info.path });
|
||
204 - if (media && media.success) {
|
||
205 - const url = `data:${media.mime};base64,${media.b64}`;
|
||
206 - parts.push({
|
||
207 - type: media.type === 'image' ? 'image_url' : 'video_url',
|
||
208 - [media.type === 'image' ? 'image_url' : 'video_url']: { url },
|
||
209 - });
|
||
210 - } else {
|
||
211 - parts.push({ type: 'text', text: token });
|
||
212 - }
|
||
213 - } else {
|
||
214 - parts.push({ type: 'text', text: token });
|
||
191 + const parts = [{ type: 'text', text: line }];
|
||
192 + for (const info of tokens) {
|
||
193 + const media = readMediafileTool(WORKSPACE, { path: info.path });
|
||
194 + if (media && media.success) {
|
||
195 + const url = `data:${media.mime};base64,${media.b64}`;
|
||
196 + parts.push({
|
||
197 + type: media.type === 'image' ? 'image_url' : 'video_url',
|
||
198 + [media.type === 'image' ? 'image_url' : 'video_url']: { url },
|
||
199 + });
|
||
200 }
|
||
216 - last = tokenRe.lastIndex;
|
||
217 - }
|
||
218 - if (last < line.length) {
|
||
219 - const tail = line.slice(last);
|
||
220 - if (tail) parts.push({ type: 'text', text: tail });
|
||
201 }
|
||
|
||
• Edited src/cli/index.js (+1 -0)
|
||
433 const content = buildUserContent(normalizedLine, currentMedia.tokens);
|
||
434 + console.log('DEBUG_USER_CONTENT', JSON.stringify(content, null, 2));
|
||
435 state.messages.push({ role: 'user', content });
|
||
|
||
• Edited src/cli/index.js (+19 -0)
|
||
68 let currentMedia = { tokens: [], text: '' };
|
||
69 +let suppressSlashMenuUntil = 0;
|
||
70 let pendingSlashTimer = null;
|
||
⋮
|
||
232
|
||
233 +process.stdin.on('data', (chunk) => {
|
||
234 + if (isRunning || commandMenuActive || !rl) return;
|
||
235 + const text = chunk ? chunk.toString() : '';
|
||
236 + if (!text) return;
|
||
237 + const looksLikePath = text.includes('file://') || /\.(png|jpe?g|gif|webp|bmp|tiff|heic|mp4|mov|avi|mkv|webm|m4v)\b/i.test(
|
||
text);
|
||
238 + if (!looksLikePath) return;
|
||
239 + suppressSlashMenuUntil = Date.now() + 200;
|
||
240 + setImmediate(() => {
|
||
241 + const applied = applyMediaTokens(rl.line || '');
|
||
242 + if (applied.tokens.length) {
|
||
243 + rl.line = applied.line;
|
||
244 + rl.cursor = rl.line.length;
|
||
245 + currentMedia = { tokens: applied.tokens, text: applied.line };
|
||
246 + refreshInputLine();
|
||
247 + }
|
||
248 + });
|
||
249 +});
|
||
250 +
|
||
251 initReadline();
|
||
|
||
• Edited src/cli/index.js (+3 -0)
|
||
288 }
|
||
289 + if (str === '/' && Date.now() < suppressSlashMenuUntil) {
|
||
290 + return;
|
||
291 + }
|
||
292 if (pendingSlashTimer && (str !== '/' || (rl.line && rl.line !== '/'))) {
|
||
|
||
• Edited src/cli/index.js (+0 -0)
|
||
|
||
• Edited src/cli/index.js (+9 -1)
|
||
297 const rawLine = rl.line || '';
|
||
298 - const applied = isSlashCommand(rawLine) ? { line: rawLine, tokens: [] } : applyMediaTokens(rawLine);
|
||
298 + let applied = { line: rawLine, tokens: [] };
|
||
299 + if (!isSlashCommand(rawLine)) {
|
||
300 + const hasToken = /\[(图片|视频) #\d+\]/.test(rawLine);
|
||
301 + if (hasToken && currentMedia.tokens.length && currentMedia.text === rawLine) {
|
||
302 + applied = { line: rawLine, tokens: currentMedia.tokens };
|
||
303 + } else {
|
||
304 + applied = applyMediaTokens(rawLine);
|
||
305 + }
|
||
306 + }
|
||
307 if (applied.line !== (rl.line || '')) {
|
||
|
||
• Edited src/cli/index.js (+0 -1)
|
||
463 const content = buildUserContent(normalizedLine, currentMedia.tokens);
|
||
464 - console.log('DEBUG_USER_CONTENT', JSON.stringify(content, null, 2));
|
||
464 state.messages.push({ role: 'user', content });
|
||
|
||
• Edited src/cli/index.js (+3 -0)
|
||
305 }
|
||
306 + if (hasToken && !applied.tokens.length && currentMedia.tokens.length) {
|
||
307 + applied = { line: rawLine, tokens: currentMedia.tokens };
|
||
308 + }
|
||
309 }
|
||
|
||
• Edited src/cli/index.js (+3 -0)
|
||
427 }
|
||
428 + if (hasToken && !applied.tokens.length && currentMedia.tokens.length) {
|
||
429 + applied = { line, tokens: currentMedia.tokens };
|
||
430 + }
|
||
431 }
|
||
|
||
• Edited src/cli/commands.js (+2 -1)
|
||
58 allow_mode: state.allowMode,
|
||
59 - token_usage: state.tokenUsage,
|
||
59 + token_usage: { prompt: 0, completion: 0, total: 0 },
|
||
60 cwd: workspace,
|
||
⋮
|
||
63 state.messages = [];
|
||
64 + state.tokenUsage = { prompt: 0, completion: 0, total: 0 };
|
||
65 console.log(`已创建新对话: ${conv.id}`);
|
||
|
||
• Edited src/cli/commands.js (+2 -2)
|
||
53 if (cmd === '/new') {
|
||
54 + state.tokenUsage = normalizeTokenUsage({ prompt: 0, completion: 0, total: 0 });
|
||
55 const conv = createConversation(workspace, {
|
||
⋮
|
||
59 allow_mode: state.allowMode,
|
||
59 - token_usage: { prompt: 0, completion: 0, total: 0 },
|
||
60 + token_usage: state.tokenUsage,
|
||
61 cwd: workspace,
|
||
⋮
|
||
64 state.messages = [];
|
||
64 - state.tokenUsage = { prompt: 0, completion: 0, total: 0 };
|
||
65 console.log(`已创建新对话: ${conv.id}`);
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
458 if (input.startsWith('/')) {
|
||
459 - const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE });
|
||
459 + const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar });
|
||
460 if (result && result.exit) return;
|
||
|
||
• Edited src/cli/commands.js (+2 -1)
|
||
27 async function handleCommand(input, ctx) {
|
||
28 - const { rl, state, config, workspace } = ctx;
|
||
28 + const { rl, state, config, workspace, statusBar } = ctx;
|
||
29 const persist = () => {
|
||
⋮
|
||
66 persist();
|
||
67 + if (statusBar) statusBar.render();
|
||
68 return { exit: false };
|
||
|
||
• Edited src/cli/index.js (+18 -0)
|
||
31 const MENU_PAGE_SIZE = 6;
|
||
32 +const DEBUG_LOG_DIR = path.join(WORKSPACE, 'logs');
|
||
33 +const DEBUG_LOG_PATH = path.join(DEBUG_LOG_DIR, 'cli-debug.log');
|
||
34 +const DEBUG_LOG_ENABLED = true;
|
||
35 const SLASH_COMMANDS = new Set(['/new', '/resume', '/allow', '/model', '/status', '/compact', '/config', '/help', '/exit']);
|
||
⋮
|
||
232
|
||
233 +function debugLog(event, data = {}) {
|
||
234 + if (!DEBUG_LOG_ENABLED) return;
|
||
235 + try {
|
||
236 + if (!fs.existsSync(DEBUG_LOG_DIR)) fs.mkdirSync(DEBUG_LOG_DIR, { recursive: true });
|
||
237 + const payload = {
|
||
238 + ts: new Date().toISOString(),
|
||
239 + event,
|
||
240 + ...data,
|
||
241 + };
|
||
242 + fs.appendFileSync(DEBUG_LOG_PATH, `${JSON.stringify(payload)}\n`);
|
||
243 + } catch (_) {}
|
||
244 +}
|
||
245 +
|
||
246 readline.emitKeypressEvents(process.stdin);
|
||
⋮
|
||
273 statusBar.render();
|
||
274 +debugLog('statusbar_init', { token_total: normalizeTokenUsage(state.tokenUsage).total || 0 });
|
||
275
|
||
⋮
|
||
476 const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar });
|
||
477 + debugLog('after_command', { cmd: input, token_total: normalizeTokenUsage(state.tokenUsage).total || 0 });
|
||
478 if (result && result.exit) return;
|
||
|
||
• Edited src/cli/commands.js (+3 -0)
|
||
67 if (statusBar) statusBar.render();
|
||
68 + if (typeof ctx.debugLog === 'function') {
|
||
69 + ctx.debugLog('command_new', { token_total: state.tokenUsage.total || 0 });
|
||
70 + }
|
||
71 return { exit: false };
|
||
|
||
• Edited src/cli/index.js (+1 -1)
|
||
475 if (input.startsWith('/')) {
|
||
476 - const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar });
|
||
476 + const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar, debugLog });
|
||
477 debugLog('after_command', { cmd: input, token_total: normalizeTokenUsage(state.tokenUsage).total || 0 });
|
||
|
||
• Edited src/cli/index.js (+7 -0)
|
||
276 process.stdin.on('keypress', (str, key) => {
|
||
277 + debugLog('keypress', { str, key: key && key.name, line: rl ? rl.line : '' });
|
||
278 if (commandMenuActive) {
|
||
⋮
|
||
345 if (rl.line === '' || rl.line === '/') {
|
||
346 + debugLog('menu_open');
|
||
347 commandMenuActive = true;
|
||
⋮
|
||
368 .then((result) => {
|
||
369 + debugLog('menu_result', { result });
|
||
370 if (result && result.chosen && !result.cancelled) {
|
||
⋮
|
||
374 .finally(() => {
|
||
375 + debugLog('menu_close', { injected: menuInjectedCommand, line: rl ? rl.line : '' });
|
||
376 commandMenuActive = false;
|
||
⋮
|
||
401 menuInjectedCommand = null;
|
||
402 + debugLog('menu_inject', { injected });
|
||
403 setImmediate(() => injectLine(injected));
|
||
⋮
|
||
428 rl.on('line', async (line) => {
|
||
429 + debugLog('line', { line });
|
||
430 if (statusBar) statusBar.render();
|
||
⋮
|
||
481 if (input.startsWith('/')) {
|
||
482 + debugLog('slash_command', { input });
|
||
483 const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar, debugLog });
|
||
|
||
• Edited src/cli/index.js (+20 -15)
|
||
65 let menuAbortController = null;
|
||
66 +let menuJustClosedInjected = false;
|
||
67 let statusBar = null;
|
||
⋮
|
||
377 commandMenuActive = false;
|
||
377 - menuAbortController = null;
|
||
378 - menuJustClosedAt = menuInjectedCommand ? Date.now() : 0;
|
||
379 - menuLastSearchTerm = menuSearchTerm;
|
||
378 + menuAbortController = null;
|
||
379 + menuJustClosedAt = menuInjectedCommand ? Date.now() : 0;
|
||
380 + menuJustClosedInjected = !!menuInjectedCommand;
|
||
381 + menuLastSearchTerm = menuSearchTerm;
|
||
382 drainStdin();
|
||
⋮
|
||
460 if (menuJustClosedAt) {
|
||
459 - const tooOld = Date.now() - menuJustClosedAt > 800;
|
||
460 - const normalizedMenu = String(menuLastSearchTerm).trim().replace(/^\/+/, '');
|
||
461 - const normalizedInput = input.replace(/^\/+/, '');
|
||
462 - if (!tooOld && (!normalizedMenu ? input === '' : normalizedInput === normalizedMenu)) {
|
||
463 - menuJustClosedAt = 0;
|
||
464 - menuLastSearchTerm = '';
|
||
465 - if (process.stdout.isTTY) {
|
||
466 - readline.moveCursor(process.stdout, 0, -1);
|
||
467 - readline.clearLine(process.stdout, 0);
|
||
468 - readline.cursorTo(process.stdout, 0);
|
||
461 + if (!menuJustClosedInjected) {
|
||
462 + const tooOld = Date.now() - menuJustClosedAt > 800;
|
||
463 + const normalizedMenu = String(menuLastSearchTerm).trim().replace(/^\/+/, '');
|
||
464 + const normalizedInput = input.replace(/^\/+/, '');
|
||
465 + if (!tooOld && (!normalizedMenu ? input === '' : normalizedInput === normalizedMenu)) {
|
||
466 + menuJustClosedAt = 0;
|
||
467 + menuLastSearchTerm = '';
|
||
468 + if (process.stdout.isTTY) {
|
||
469 + readline.moveCursor(process.stdout, 0, -1);
|
||
470 + readline.clearLine(process.stdout, 0);
|
||
471 + readline.cursorTo(process.stdout, 0);
|
||
472 + }
|
||
473 + promptWithStatus();
|
||
474 + return;
|
||
475 }
|
||
470 - promptWithStatus();
|
||
471 - return;
|
||
476 }
|
||
⋮
|
||
478 menuLastSearchTerm = '';
|
||
479 + menuJustClosedInjected = false;
|
||
480 }
|
||
|
||
• Edited src/cli/index.js (+1 -25)
|
||
31 const MENU_PAGE_SIZE = 6;
|
||
32 -const DEBUG_LOG_DIR = path.join(WORKSPACE, 'logs');
|
||
33 -const DEBUG_LOG_PATH = path.join(DEBUG_LOG_DIR, 'cli-debug.log');
|
||
34 -const DEBUG_LOG_ENABLED = true;
|
||
32 const SLASH_COMMANDS = new Set(['/new', '/resume', '/allow', '/model', '/status', '/compact', '/config', '/help', '/exit']);
|
||
⋮
|
||
230
|
||
234 -function debugLog(event, data = {}) {
|
||
235 - if (!DEBUG_LOG_ENABLED) return;
|
||
236 - try {
|
||
237 - if (!fs.existsSync(DEBUG_LOG_DIR)) fs.mkdirSync(DEBUG_LOG_DIR, { recursive: true });
|
||
238 - const payload = {
|
||
239 - ts: new Date().toISOString(),
|
||
240 - event,
|
||
241 - ...data,
|
||
242 - };
|
||
243 - fs.appendFileSync(DEBUG_LOG_PATH, `${JSON.stringify(payload)}\n`);
|
||
244 - } catch (_) {}
|
||
245 -}
|
||
231
|
||
⋮
|
||
259 statusBar.render();
|
||
275 -debugLog('statusbar_init', { token_total: normalizeTokenUsage(state.tokenUsage).total || 0 });
|
||
260
|
||
261 process.stdin.on('keypress', (str, key) => {
|
||
278 - debugLog('keypress', { str, key: key && key.name, line: rl ? rl.line : '' });
|
||
262 if (commandMenuActive) {
|
||
⋮
|
||
329 if (rl.line === '' || rl.line === '/') {
|
||
347 - debugLog('menu_open');
|
||
330 commandMenuActive = true;
|
||
⋮
|
||
351 .then((result) => {
|
||
370 - debugLog('menu_result', { result });
|
||
352 if (result && result.chosen && !result.cancelled) {
|
||
⋮
|
||
356 .finally(() => {
|
||
376 - debugLog('menu_close', { injected: menuInjectedCommand, line: rl ? rl.line : '' });
|
||
357 commandMenuActive = false;
|
||
⋮
|
||
383 menuInjectedCommand = null;
|
||
404 - debugLog('menu_inject', { injected });
|
||
384 setImmediate(() => injectLine(injected));
|
||
⋮
|
||
409 rl.on('line', async (line) => {
|
||
431 - debugLog('line', { line });
|
||
410 if (statusBar) statusBar.render();
|
||
⋮
|
||
464 if (input.startsWith('/')) {
|
||
487 - debugLog('slash_command', { input });
|
||
488 - const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar, debugLog });
|
||
489 - debugLog('after_command', { cmd: input, token_total: normalizeTokenUsage(state.tokenUsage).total || 0 });
|
||
465 + const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar });
|
||
466 if (result && result.exit) return;
|
||
|
||
• Edited src/cli/commands.js (+0 -3)
|
||
67 if (statusBar) statusBar.render();
|
||
68 - if (typeof ctx.debugLog === 'function') {
|
||
69 - ctx.debugLog('command_new', { token_total: state.tokenUsage.total || 0 });
|
||
70 - }
|
||
68 return { exit: false };
|
||
|
||
• Edited src/cli/index.js (+9 -0)
|
||
230
|
||
231 +function stopSpinnerForCancel(spinner, thinkingActive, showThinkingLabel, thinkingMode) {
|
||
232 + if (!spinner) return;
|
||
233 + if (thinkingMode && (thinkingActive || showThinkingLabel)) {
|
||
234 + spinner.stop('∙ 停止思考');
|
||
235 + } else {
|
||
236 + spinner.stopSilent();
|
||
237 + }
|
||
238 +}
|
||
239 +
|
||
240
|
||
|
||
• Edited src/cli/index.js (+2 -2)
|
||
655 if (escPendingCancel) {
|
||
656 - spinner.stopSilent();
|
||
656 + stopSpinnerForCancel(spinner, thinkingActive, showThinkingLabel, state.thinkingMode);
|
||
657 } else if (state.thinkingMode) {
|
||
⋮
|
||
678 clearTimeout(thinkingDelay);
|
||
679 - spinner.stopSilent();
|
||
679 + stopSpinnerForCancel(spinner, thinkingActive, showThinkingLabel, state.thinkingMode);
|
||
680 showCursor();
|
||
|
||
• Edited src/cli/commands.js (+18 -10)
|
||
26
|
||
27 +function printNotice(message) {
|
||
28 + console.log('');
|
||
29 + console.log(message);
|
||
30 + console.log('');
|
||
31 +}
|
||
32 +
|
||
33 async function handleCommand(input, ctx) {
|
||
⋮
|
||
70 state.messages = [];
|
||
65 - console.log(`已创建新对话: ${conv.id}`);
|
||
71 + printNotice(`已创建新对话: ${conv.id}`);
|
||
72 persist();
|
||
⋮
|
||
80 if (!conv) {
|
||
75 - console.log('未找到对话');
|
||
81 + printNotice('未找到对话');
|
||
82 return { exit: false };
|
||
⋮
|
||
88 state.tokenUsage = normalizeTokenUsage(conv.metadata?.token_usage);
|
||
83 - console.log(`已加载对话: ${conv.id}`);
|
||
89 + printNotice(`已加载对话: ${conv.id}`);
|
||
90 renderConversation(state.messages);
|
||
⋮
|
||
96 if (!items.length) {
|
||
91 - console.log('暂无对话记录');
|
||
97 + printNotice('暂无对话记录');
|
||
98 return { exit: false };
|
||
⋮
|
||
102 if (!filtered.length) {
|
||
97 - console.log('暂无可恢复的对话');
|
||
103 + printNotice('暂无可恢复的对话');
|
||
104 return { exit: false };
|
||
⋮
|
||
130 if (!conv) {
|
||
125 - console.log('未找到对话');
|
||
131 + printNotice('未找到对话');
|
||
132 return { exit: false };
|
||
⋮
|
||
138 state.tokenUsage = normalizeTokenUsage(conv.metadata?.token_usage);
|
||
133 - console.log(`已加载对话: ${conv.id}`);
|
||
139 + printNotice(`已加载对话: ${conv.id}`);
|
||
140 renderConversation(state.messages);
|
||
⋮
|
||
158 state.allowMode = selected;
|
||
153 - console.log(`运行模式已切换为: ${state.allowMode}`);
|
||
159 + printNotice(`运行模式已切换为: ${state.allowMode}`);
|
||
160 persist();
|
||
⋮
|
||
180 state.thinkingMode = mode === 'thinking';
|
||
175 - console.log(`模型已切换为: ${state.modelKey} | 思考模式: ${mode}`);
|
||
181 + printNotice(`模型已切换为: ${state.modelKey} | 思考模式: ${mode}`);
|
||
182 persist();
|
||
⋮
|
||
204 if (cmd === '/config') {
|
||
205 + console.log('');
|
||
206 console.log(`base_url: ${config.base_url}`);
|
||
⋮
|
||
208 console.log(`apikey: ${maskKey(config.api_key)}`);
|
||
209 + console.log('');
|
||
210 return { exit: false };
|
||
⋮
|
||
246 spinner.stop('○');
|
||
239 - console.log(`压缩完成:${oldId} -> ${state.conversation.id}`);
|
||
247 + printNotice(`压缩完成:${oldId} -> ${state.conversation.id}`);
|
||
248 persist();
|
||
|
||
• Edited src/cli/commands.js (+3 -3)
|
||
155 ];
|
||
156 - const selected = await runSelect({ rl, message: 'Update Permissions', choices, pageSize: 6 });
|
||
156 + const selected = await runSelect({ rl, message: '', choices, pageSize: 6 });
|
||
157 if (selected) {
|
||
⋮
|
||
168 ];
|
||
169 - const model = await runSelect({ rl, message: 'Update Model', choices: modelChoices, pageSize: 6 });
|
||
169 + const model = await runSelect({ rl, message: '', choices: modelChoices, pageSize: 6 });
|
||
170 if (!model) return { exit: false };
|
||
⋮
|
||
177 ];
|
||
178 - const mode = await runSelect({ rl, message: 'Update Thinking Mode', choices: thinkingChoices, pageSize: 6 });
|
||
178 + const mode = await runSelect({ rl, message: '', choices: thinkingChoices, pageSize: 6 });
|
||
179 if (mode) {
|
||
|
||
• Edited src/cli/index.js (+6 -0)
|
||
47
|
||
48 +function printNotice(message) {
|
||
49 + console.log('');
|
||
50 + console.log(message);
|
||
51 + console.log('');
|
||
52 +}
|
||
53 +
|
||
54 renderBanner({
|
||
|
||
• Edited src/cli/index.js (+5 -1)
|
||
478
|
||
479 - if (input.startsWith('/')) {
|
||
479 + if (input.startsWith('/') && isSlashCommand(input)) {
|
||
480 const result = await handleCommand(input, { rl, state, config, workspace: WORKSPACE, statusBar });
|
||
⋮
|
||
485
|
||
486 + if (input.startsWith('/') && !isSlashCommand(input)) {
|
||
487 + printNotice(`无效的命令“${input}”`);
|
||
488 + }
|
||
489 +
|
||
490 console.log('');
|
||
|
||
• Edited src/ui/command_menu.js (+5 -3)
|
||
19 let latestInput = '';
|
||
20 + let lastCount = 0;
|
||
21
|
||
⋮
|
||
61 : COMMAND_CHOICES;
|
||
62 + lastCount = filtered.length;
|
||
63 return filtered.map((c) => ({ name: format(c), value: c.value }));
|
||
⋮
|
||
65 }, abortSignal ? { signal: abortSignal, clearPromptOnDone: true } : { clearPromptOnDone: true });
|
||
64 - return { chosen, term: latestInput, cancelled: false };
|
||
66 + return { chosen, term: latestInput, cancelled: false, noResults: lastCount === 0 };
|
||
67 } catch (err) {
|
||
68 if (err && (err.name === 'AbortPromptError' || err.name === 'CancelPromptError')) {
|
||
67 - return { chosen: null, term: latestInput, cancelled: true };
|
||
69 + return { chosen: null, term: latestInput, cancelled: true, noResults: lastCount === 0 };
|
||
70 }
|
||
71 console.log('指令菜单不可用,请先安装依赖: npm i @inquirer/search');
|
||
70 - return { chosen: null, term: '', cancelled: true };
|
||
72 + return { chosen: null, term: '', cancelled: true, noResults: false };
|
||
73 } finally {
|
||
|
||
• Edited src/cli/index.js (+11 -0)
|
||
368 menuInjectedCommand = result.chosen;
|
||
369 + return;
|
||
370 + }
|
||
371 + if (
|
||
372 + result &&
|
||
373 + !result.cancelled &&
|
||
374 + !result.chosen &&
|
||
375 + result.noResults &&
|
||
376 + typeof result.term === 'string' &&
|
||
377 + result.term.trim().length > 0
|
||
378 + ) {
|
||
379 + menuInjectedCommand = `/${result.term.trim()}`;
|
||
380 }
|
||
|
||
• Edited src/ui/command_menu.js (+12 -6)
|
||
16
|
||
17 +function hasCommandMatch(term) {
|
||
18 + if (!term) return false;
|
||
19 + const t = term.toLowerCase();
|
||
20 + return COMMAND_CHOICES.some(
|
||
21 + (c) => c.value.toLowerCase().includes(t) || c.desc.toLowerCase().includes(t)
|
||
22 + );
|
||
23 +}
|
||
24 +
|
||
25 async function openCommandMenu(options) {
|
||
⋮
|
||
27 let latestInput = '';
|
||
20 - let lastCount = 0;
|
||
28
|
||
⋮
|
||
68 : COMMAND_CHOICES;
|
||
62 - lastCount = filtered.length;
|
||
69 return filtered.map((c) => ({ name: format(c), value: c.value }));
|
||
⋮
|
||
71 }, abortSignal ? { signal: abortSignal, clearPromptOnDone: true } : { clearPromptOnDone: true });
|
||
66 - return { chosen, term: latestInput, cancelled: false, noResults: lastCount === 0 };
|
||
72 + return { chosen, term: latestInput, cancelled: false };
|
||
73 } catch (err) {
|
||
74 if (err && (err.name === 'AbortPromptError' || err.name === 'CancelPromptError')) {
|
||
69 - return { chosen: null, term: latestInput, cancelled: true, noResults: lastCount === 0 };
|
||
75 + return { chosen: null, term: latestInput, cancelled: true };
|
||
76 }
|
||
77 console.log('指令菜单不可用,请先安装依赖: npm i @inquirer/search');
|
||
72 - return { chosen: null, term: '', cancelled: true, noResults: false };
|
||
78 + return { chosen: null, term: '', cancelled: true };
|
||
79 } finally {
|
||
⋮
|
||
87
|
||
82 -module.exports = { openCommandMenu };
|
||
88 +module.exports = { openCommandMenu, hasCommandMatch };
|
||
|
||
• Edited src/cli/index.js (+12 -12)
|
||
13 const { executeTool } = require('../tools/dispatcher');
|
||
14 -const { openCommandMenu } = require('../ui/command_menu');
|
||
14 +const { openCommandMenu, hasCommandMatch } = require('../ui/command_menu');
|
||
15 const { handleCommand } = require('./commands');
|
||
⋮
|
||
281 }
|
||
282 + return;
|
||
283 + }
|
||
284 + if (key && key.name === 'return') {
|
||
285 + const term = String(menuSearchTerm || '').replace(/^\/+/, '').trim();
|
||
286 + if (term && !hasCommandMatch(term)) {
|
||
287 + menuInjectedCommand = `/${term}`;
|
||
288 + if (menuAbortController && !menuAbortController.signal.aborted) {
|
||
289 + menuAbortController.abort();
|
||
290 + }
|
||
291 + }
|
||
292 + return;
|
||
293 }
|
||
⋮
|
||
379 menuInjectedCommand = result.chosen;
|
||
369 - return;
|
||
370 - }
|
||
371 - if (
|
||
372 - result &&
|
||
373 - !result.cancelled &&
|
||
374 - !result.chosen &&
|
||
375 - result.noResults &&
|
||
376 - typeof result.term === 'string' &&
|
||
377 - result.term.trim().length > 0
|
||
378 - ) {
|
||
379 - menuInjectedCommand = `/${result.term.trim()}`;
|
||
380 }
|
||
|
||
• Edited src/ui/command_menu.js (+1 -0)
|
||
47 highlight: (text) => (colorEnabled ? `\x1b[94m${text}${resetAnsi}` : text),
|
||
48 + error: () => '无效的指令',
|
||
49 keysHelpTip: () => '',
|
||
|
||
• Edited src/cli/index.js (+7 -2)
|
||
69 let menuJustClosedInjected = false;
|
||
70 +let menuInvalidCommand = '';
|
||
71 let statusBar = null;
|
||
⋮
|
||
287 if (term && !hasCommandMatch(term)) {
|
||
287 - menuInjectedCommand = `/${term}`;
|
||
288 + menuInvalidCommand = `/${term}`;
|
||
289 if (menuAbortController && !menuAbortController.signal.aborted) {
|
||
⋮
|
||
385 menuAbortController = null;
|
||
385 - menuJustClosedAt = menuInjectedCommand ? Date.now() : 0;
|
||
386 + menuJustClosedAt = menuInjectedCommand || menuInvalidCommand ? Date.now() : 0;
|
||
387 menuJustClosedInjected = !!menuInjectedCommand;
|
||
⋮
|
||
406 }
|
||
407 + if (menuInvalidCommand) {
|
||
408 + printNotice(`无效的命令“${menuInvalidCommand}”`);
|
||
409 + menuInvalidCommand = '';
|
||
410 + }
|
||
411 promptWithStatus(true);
|
||
|
||
• Edited src/ui/select_prompt.js (+2 -2)
|
||
4
|
||
5 -async function runSelect({ rl, message, choices, pageSize = 6 }) {
|
||
5 +async function runSelect({ rl, message, choices, pageSize = 6, clearPromptOnDone = true }) {
|
||
6 rl.pause();
|
||
⋮
|
||
25 },
|
||
26 - }, { clearPromptOnDone: true });
|
||
26 + }, { clearPromptOnDone });
|
||
27 readline.clearLine(process.stdout, 0);
|
||
|
||
• Edited src/cli/commands.js (+8 -1)
|
||
168 ];
|
||
169 - const model = await runSelect({ rl, message: '', choices: modelChoices, pageSize: 6 });
|
||
169 + const model = await runSelect({
|
||
170 + rl,
|
||
171 + message: '',
|
||
172 + choices: modelChoices,
|
||
173 + pageSize: 6,
|
||
174 + clearPromptOnDone: false,
|
||
175 + });
|
||
176 if (!model) return { exit: false };
|
||
⋮
|
||
178 state.modelId = config.model_id || 'kimi-k2.5';
|
||
179 + console.log('');
|
||
180
|
||
|
||
• Edited src/ui/select_prompt.js (+2 -2)
|
||
4
|
||
5 -async function runSelect({ rl, message, choices, pageSize = 6, clearPromptOnDone = true }) {
|
||
5 +async function runSelect({ rl, message, choices, pageSize = 6 }) {
|
||
6 rl.pause();
|
||
⋮
|
||
25 },
|
||
26 - }, { clearPromptOnDone });
|
||
26 + }, { clearPromptOnDone: true });
|
||
27 readline.clearLine(process.stdout, 0);
|
||
|
||
• Edited src/cli/commands.js (+1 -8)
|
||
168 ];
|
||
169 - const model = await runSelect({
|
||
170 - rl,
|
||
171 - message: '',
|
||
172 - choices: modelChoices,
|
||
173 - pageSize: 6,
|
||
174 - clearPromptOnDone: false,
|
||
175 - });
|
||
169 + const model = await runSelect({ rl, message: '', choices: modelChoices, pageSize: 6 });
|
||
170 if (!model) return { exit: false };
|
||
⋮
|
||
172 state.modelId = config.model_id || 'kimi-k2.5';
|
||
179 - console.log('');
|
||
173
|
||
|
||
• Edited src/ui/select_prompt.js (+3 -0)
|
||
26 }, { clearPromptOnDone: true });
|
||
27 + if (process.stdout.isTTY) {
|
||
28 + readline.clearScreenDown(process.stdout);
|
||
29 + }
|
||
30 readline.clearLine(process.stdout, 0);
|
||
|
||
• Edited src/ui/select_prompt.js (+28 -0)
|
||
2
|
||
3 +const fs = require('fs');
|
||
4 +const path = require('path');
|
||
5 const readline = require('readline');
|
||
6
|
||
7 +function debugLog(event, data = {}) {
|
||
8 + if (process.env.EAGENT_DEBUG !== '1') return;
|
||
9 + try {
|
||
10 + const dir = path.join(process.cwd(), 'logs');
|
||
11 + fs.mkdirSync(dir, { recursive: true });
|
||
12 + const payload = {
|
||
13 + ts: new Date().toISOString(),
|
||
14 + event,
|
||
15 + ...data,
|
||
16 + };
|
||
17 + fs.appendFileSync(path.join(dir, 'cli-debug.log'), `${JSON.stringify(payload)}\n`);
|
||
18 + } catch (_) {}
|
||
19 +}
|
||
20 +
|
||
21 async function runSelect({ rl, message, choices, pageSize = 6 }) {
|
||
⋮
|
||
26 readline.cursorTo(process.stdout, 0);
|
||
27 + debugLog('select_open', {
|
||
28 + message: message || '',
|
||
29 + choices: Array.isArray(choices) ? choices.length : 0,
|
||
30 + pageSize,
|
||
31 + tty: !!process.stdout.isTTY,
|
||
32 + rows: process.stdout.rows,
|
||
33 + columns: process.stdout.columns,
|
||
34 + });
|
||
35
|
||
⋮
|
||
37 const { default: select } = await import('@inquirer/select');
|
||
38 + debugLog('select_render');
|
||
39 const value = await select({
|
||
⋮
|
||
51 }, { clearPromptOnDone: true });
|
||
52 + debugLog('select_done', { value: value || null });
|
||
53 if (process.stdout.isTTY) {
|
||
⋮
|
||
57 readline.cursorTo(process.stdout, 0);
|
||
58 + debugLog('select_cleared');
|
||
59 return value;
|
||
⋮
|
||
65 } catch (_) {}
|
||
66 + debugLog('select_resume');
|
||
67 }
|
||
|
||
• Edited src/ui/select_prompt.js (+4 -0)
|
||
26 readline.cursorTo(process.stdout, 0);
|
||
27 + if (process.stdout.isTTY) {
|
||
28 + // Allow the select prompt to use the full screen; status bar will reapply later.
|
||
29 + process.stdout.write('\x1b[r');
|
||
30 + }
|
||
31 debugLog('select_open', {
|
||
|
||
• Edited src/ui/select_prompt.js (+2 -2)
|
||
27 if (process.stdout.isTTY) {
|
||
28 - // Allow the select prompt to use the full screen; status bar will reapply later.
|
||
29 - process.stdout.write('\x1b[r');
|
||
28 + process.stdout.write('\x1b7'); // save cursor position
|
||
29 }
|
||
⋮
|
||
56 if (process.stdout.isTTY) {
|
||
57 + process.stdout.write('\x1b8'); // restore cursor position
|
||
58 readline.clearScreenDown(process.stdout);
|
||
|
||
• Edited src/cli/commands.js (+2 -1)
|
||
172 state.modelId = config.model_id || 'kimi-k2.5';
|
||
173 + printNotice(`模型已切换为: ${state.modelKey}`);
|
||
174
|
||
⋮
|
||
181 state.thinkingMode = mode === 'thinking';
|
||
181 - printNotice(`模型已切换为: ${state.modelKey} | 思考模式: ${mode}`);
|
||
182 + printNotice(`思考模式: ${mode}`);
|
||
183 persist();
|
||
|
||
• Edited src/cli/commands.js (+8 -0)
|
||
165 if (cmd === '/model') {
|
||
166 + if (statusBar) statusBar.setEnabled(false);
|
||
167 + try {
|
||
168 const modelChoices = [
|
||
⋮
|
||
186 }
|
||
187 + } finally {
|
||
188 + if (statusBar) {
|
||
189 + statusBar.setEnabled(true);
|
||
190 + statusBar.render();
|
||
191 + }
|
||
192 + }
|
||
193 return { exit: false };
|
||
|
||
• Edited src/ui/select_prompt.js (+1 -0)
|
||
28 process.stdout.write('\x1b7'); // save cursor position
|
||
29 + process.stdout.write('\x1b[r'); // reset scroll region for full-screen menus
|
||
30 }
|
||
|
||
• Edited src/ui/select_prompt.js (+1 -2)
|
||
27 if (process.stdout.isTTY) {
|
||
28 - process.stdout.write('\x1b7'); // save cursor position
|
||
28 process.stdout.write('\x1b[r'); // reset scroll region for full-screen menus
|
||
⋮
|
||
56 if (process.stdout.isTTY) {
|
||
58 - process.stdout.write('\x1b8'); // restore cursor position
|
||
57 + process.stdout.write('\x1b[r');
|
||
58 readline.clearScreenDown(process.stdout);
|
||
|
||
• Edited src/ui/select_prompt.js (+9 -7)
|
||
20
|
||
21 -async function runSelect({ rl, message, choices, pageSize = 6 }) {
|
||
21 +async function runSelect({ rl, message, choices, pageSize = 6, clearOnDone = true }) {
|
||
22 rl.pause();
|
||
⋮
|
||
53 },
|
||
54 - }, { clearPromptOnDone: true });
|
||
54 + }, { clearPromptOnDone: clearOnDone });
|
||
55 debugLog('select_done', { value: value || null });
|
||
56 - if (process.stdout.isTTY) {
|
||
57 - process.stdout.write('\x1b[r');
|
||
58 - readline.clearScreenDown(process.stdout);
|
||
56 + if (clearOnDone) {
|
||
57 + if (process.stdout.isTTY) {
|
||
58 + process.stdout.write('\x1b[r');
|
||
59 + readline.clearScreenDown(process.stdout);
|
||
60 + }
|
||
61 + readline.clearLine(process.stdout, 0);
|
||
62 + readline.cursorTo(process.stdout, 0);
|
||
63 }
|
||
60 - readline.clearLine(process.stdout, 0);
|
||
61 - readline.cursorTo(process.stdout, 0);
|
||
64 debugLog('select_cleared');
|
||
|
||
• Edited src/cli/commands.js (+14 -2)
|
||
170 ];
|
||
171 - const model = await runSelect({ rl, message: '', choices: modelChoices, pageSize: 6 });
|
||
171 + const model = await runSelect({
|
||
172 + rl,
|
||
173 + message: '',
|
||
174 + choices: modelChoices,
|
||
175 + pageSize: 6,
|
||
176 + clearOnDone: false,
|
||
177 + });
|
||
178 if (!model) return { exit: false };
|
||
⋮
|
||
186 ];
|
||
181 - const mode = await runSelect({ rl, message: '', choices: thinkingChoices, pageSize: 6 });
|
||
187 + const mode = await runSelect({
|
||
188 + rl,
|
||
189 + message: '',
|
||
190 + choices: thinkingChoices,
|
||
191 + pageSize: 6,
|
||
192 + clearOnDone: false,
|
||
193 + });
|
||
194 if (mode) {
|
||
|
||
• Edited src/ui/select_prompt.js (+7 -9)
|
||
20
|
||
21 -async function runSelect({ rl, message, choices, pageSize = 6, clearOnDone = true }) {
|
||
21 +async function runSelect({ rl, message, choices, pageSize = 6 }) {
|
||
22 rl.pause();
|
||
⋮
|
||
53 },
|
||
54 - }, { clearPromptOnDone: clearOnDone });
|
||
54 + }, { clearPromptOnDone: true });
|
||
55 debugLog('select_done', { value: value || null });
|
||
56 - if (clearOnDone) {
|
||
57 - if (process.stdout.isTTY) {
|
||
58 - process.stdout.write('\x1b[r');
|
||
59 - readline.clearScreenDown(process.stdout);
|
||
60 - }
|
||
61 - readline.clearLine(process.stdout, 0);
|
||
62 - readline.cursorTo(process.stdout, 0);
|
||
56 + if (process.stdout.isTTY) {
|
||
57 + process.stdout.write('\x1b[r');
|
||
58 + readline.clearScreenDown(process.stdout);
|
||
59 }
|
||
60 + readline.clearLine(process.stdout, 0);
|
||
61 + readline.cursorTo(process.stdout, 0);
|
||
62 debugLog('select_cleared');
|
||
|
||
• Edited src/cli/commands.js (+2 -14)
|
||
170 ];
|
||
171 - const model = await runSelect({
|
||
172 - rl,
|
||
173 - message: '',
|
||
174 - choices: modelChoices,
|
||
175 - pageSize: 6,
|
||
176 - clearOnDone: false,
|
||
177 - });
|
||
171 + const model = await runSelect({ rl, message: '', choices: modelChoices, pageSize: 6 });
|
||
172 if (!model) return { exit: false };
|
||
⋮
|
||
180 ];
|
||
187 - const mode = await runSelect({
|
||
188 - rl,
|
||
189 - message: '',
|
||
190 - choices: thinkingChoices,
|
||
191 - pageSize: 6,
|
||
192 - clearOnDone: false,
|
||
193 - });
|
||
181 + const mode = await runSelect({ rl, message: '', choices: thinkingChoices, pageSize: 6 });
|
||
182 if (mode) { |