@ww_nero/mini-cli 1.0.62 → 1.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/config.js +401 -376
- package/src/tools/bash.js +12 -12
- package/src/tools/index.js +12 -2
- package/src/tools/mcp.js +5 -4
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,376 +1,401 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
const CONFIG_DIR = path.join(os.homedir(), '.mini');
|
|
6
|
-
const FILE_NAMES = {
|
|
7
|
-
chat: 'chat.json',
|
|
8
|
-
commit: 'commit.json',
|
|
9
|
-
mcp: 'mcp.json',
|
|
10
|
-
settings: 'settings.json',
|
|
11
|
-
worktree: 'worktree.json',
|
|
12
|
-
mini: 'MINI.md'
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const DEFAULT_ALLOWED_COMMANDS = [
|
|
16
|
-
'rm', 'rmdir', 'touch', 'mkdir', 'cd', 'cp', 'mv', 'node', 'npm', 'pkill', 'kill',
|
|
17
|
-
'curl', 'ls', 'pwd', 'grep', 'cat', 'echo', 'sed', 'head', 'tail', 'find', 'true',
|
|
18
|
-
'false', 'pip', 'python', 'ps', 'lsof', 'git', 'pandoc'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const DEFAULT_COMPACT_TOKEN_THRESHOLD = 65536;
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (!
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
const
|
|
279
|
-
if (
|
|
280
|
-
return
|
|
281
|
-
}
|
|
282
|
-
const
|
|
283
|
-
targetList
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.mini');
|
|
6
|
+
const FILE_NAMES = {
|
|
7
|
+
chat: 'chat.json',
|
|
8
|
+
commit: 'commit.json',
|
|
9
|
+
mcp: 'mcp.json',
|
|
10
|
+
settings: 'settings.json',
|
|
11
|
+
worktree: 'worktree.json',
|
|
12
|
+
mini: 'MINI.md'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const DEFAULT_ALLOWED_COMMANDS = [
|
|
16
|
+
'rm', 'rmdir', 'touch', 'mkdir', 'cd', 'cp', 'mv', 'node', 'npm', 'pkill', 'kill',
|
|
17
|
+
'curl', 'ls', 'pwd', 'grep', 'cat', 'echo', 'sed', 'head', 'tail', 'find', 'true',
|
|
18
|
+
'false', 'pip', 'python', 'ps', 'lsof', 'git', 'pandoc'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const DEFAULT_TOOL_RESPONSE_MAX_TOKENS = 65536;
|
|
22
|
+
const DEFAULT_COMPACT_TOKEN_THRESHOLD = 65536;
|
|
23
|
+
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
24
|
+
const DEFAULT_OUTPUT_MAX_LENGTH = 12000;
|
|
25
|
+
const DEFAULT_EXECUTION_TIMEOUT = 300000;
|
|
26
|
+
const DEFAULT_SERVICE_BOOT_WINDOW = 5000;
|
|
27
|
+
const DEFAULT_LARGE_FILE_LINE_THRESHOLD = 2000;
|
|
28
|
+
const COMPACT_SUMMARY_PROMPT = `请对以下对话进行总结,用于上下文压缩。请按以下格式输出:
|
|
29
|
+
|
|
30
|
+
## 问题背景
|
|
31
|
+
简要描述用户的原始问题和目标,包括涉及的重要文件路径。
|
|
32
|
+
|
|
33
|
+
## 已完成工作
|
|
34
|
+
列出已经完成的任务或环节:
|
|
35
|
+
- 已搜索/阅读的文件
|
|
36
|
+
- 已修改的代码
|
|
37
|
+
- 已执行的操作
|
|
38
|
+
- 重要的中间结果
|
|
39
|
+
|
|
40
|
+
## 待继续任务
|
|
41
|
+
根据对话上下文,描述接下来需要继续完成的工作:
|
|
42
|
+
- 未完成的任务
|
|
43
|
+
- 需要进一步处理的问题
|
|
44
|
+
- 用户最后一次请求的具体内容
|
|
45
|
+
|
|
46
|
+
请确保总结完整、准确,以便在压缩后能够继续完成用户的任务。`;
|
|
47
|
+
const detectCommandPath = () => {
|
|
48
|
+
if (process.platform === 'win32') return 'C:/Windows/System32/wsl.exe';
|
|
49
|
+
if (process.platform === 'darwin') return '/bin/zsh';
|
|
50
|
+
return '/bin/bash';
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const DEFAULT_CHAT_ENDPOINTS = [
|
|
54
|
+
{
|
|
55
|
+
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
56
|
+
key: 'your-api-key',
|
|
57
|
+
model: 'gemini-flash-latest',
|
|
58
|
+
alias: 'Gemini Flash',
|
|
59
|
+
name: 'gemini-flash-latest',
|
|
60
|
+
think: true,
|
|
61
|
+
price: 0,
|
|
62
|
+
options: {
|
|
63
|
+
extra_body: {
|
|
64
|
+
google: {
|
|
65
|
+
thinking_config: {
|
|
66
|
+
thinking_budget: -1,
|
|
67
|
+
include_thoughts: false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const DEFAULT_COMMIT_MODEL = {
|
|
76
|
+
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
77
|
+
key: 'your-api-key',
|
|
78
|
+
model: 'gemini-flash-latest',
|
|
79
|
+
options: {
|
|
80
|
+
reasoning_effort: 'none'
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const DEFAULT_WORKTREE_COMMANDS = [
|
|
85
|
+
{ base: 'codex', prompt: 'codex --full-auto {prompt}', resume: 'codex --full-auto resume' },
|
|
86
|
+
{ base: 'claude', prompt: 'claude --permission-mode acceptEdits {prompt}', resume: 'claude --permission-mode acceptEdits --continue' },
|
|
87
|
+
{ base: 'gemini', prompt: 'gemini -m pro --approval-mode=auto_edit -i {prompt}', resume: 'gemini -m pro --approval-mode=auto_edit --resume' }
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const DEFAULT_SETTINGS = {
|
|
91
|
+
mcpServers: {},
|
|
92
|
+
tools: {},
|
|
93
|
+
commands: [],
|
|
94
|
+
maxToolTokens: DEFAULT_TOOL_RESPONSE_MAX_TOKENS,
|
|
95
|
+
compactTokenThreshold: DEFAULT_COMPACT_TOKEN_THRESHOLD,
|
|
96
|
+
allowedCommands: [...DEFAULT_ALLOWED_COMMANDS],
|
|
97
|
+
mcpToolTimeout: DEFAULT_MCP_TOOL_TIMEOUT_MS,
|
|
98
|
+
outputMaxLength: DEFAULT_OUTPUT_MAX_LENGTH,
|
|
99
|
+
executionTimeout: DEFAULT_EXECUTION_TIMEOUT,
|
|
100
|
+
serviceBootWindow: DEFAULT_SERVICE_BOOT_WINDOW,
|
|
101
|
+
largeFileLineThreshold: DEFAULT_LARGE_FILE_LINE_THRESHOLD
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const DEFAULT_MINI_CONTENT = '# 在此填写全局系统指令。\n';
|
|
105
|
+
|
|
106
|
+
class ConfigError extends Error {
|
|
107
|
+
constructor(message, configPath) {
|
|
108
|
+
super(message);
|
|
109
|
+
this.name = 'ConfigError';
|
|
110
|
+
this.configPath = configPath;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const isPlainObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
|
|
115
|
+
|
|
116
|
+
const ensureDir = (dirPath) => {
|
|
117
|
+
if (!fs.existsSync(dirPath)) {
|
|
118
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const writeJsonFile = (filePath, data) => {
|
|
123
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const ensureFile = (filePath, data) => {
|
|
127
|
+
if (fs.existsSync(filePath)) return false;
|
|
128
|
+
if (typeof data === 'string') {
|
|
129
|
+
fs.writeFileSync(filePath, data, 'utf8');
|
|
130
|
+
} else {
|
|
131
|
+
writeJsonFile(filePath, data);
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const getConfigPath = (key) => path.join(CONFIG_DIR, FILE_NAMES[key]);
|
|
137
|
+
const getDefaultConfigPath = () => getConfigPath('chat');
|
|
138
|
+
|
|
139
|
+
const readJsonFile = (filePath, fallback = {}) => {
|
|
140
|
+
try {
|
|
141
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
142
|
+
if (!raw.trim()) return fallback;
|
|
143
|
+
return JSON.parse(raw);
|
|
144
|
+
} catch (_) {
|
|
145
|
+
return fallback;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const extractEndpointList = (parsed) => {
|
|
150
|
+
if (Array.isArray(parsed)) return parsed;
|
|
151
|
+
if (isPlainObject(parsed)) {
|
|
152
|
+
if (Array.isArray(parsed.endpoints)) return parsed.endpoints;
|
|
153
|
+
if (parsed.endpoint) return [parsed.endpoint];
|
|
154
|
+
}
|
|
155
|
+
return [];
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const ensureConfigFiles = () => {
|
|
159
|
+
ensureDir(CONFIG_DIR);
|
|
160
|
+
const createdFiles = [];
|
|
161
|
+
|
|
162
|
+
if (ensureFile(getConfigPath('chat'), { endpoints: DEFAULT_CHAT_ENDPOINTS })) {
|
|
163
|
+
createdFiles.push(FILE_NAMES.chat);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (ensureFile(getConfigPath('commit'), { model: DEFAULT_COMMIT_MODEL })) {
|
|
167
|
+
createdFiles.push(FILE_NAMES.commit);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (ensureFile(getConfigPath('mcp'), { mcpServers: {} })) {
|
|
171
|
+
createdFiles.push(FILE_NAMES.mcp);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (ensureFile(getConfigPath('settings'), DEFAULT_SETTINGS)) {
|
|
175
|
+
createdFiles.push(FILE_NAMES.settings);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (ensureFile(getConfigPath('worktree'), { commands: DEFAULT_WORKTREE_COMMANDS })) {
|
|
179
|
+
createdFiles.push(FILE_NAMES.worktree);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (ensureFile(getConfigPath('mini'), DEFAULT_MINI_CONTENT)) {
|
|
183
|
+
createdFiles.push(FILE_NAMES.mini);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { configDir: CONFIG_DIR, createdFiles };
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const normalizePositiveInteger = (value, fallback) => {
|
|
190
|
+
const numberValue = Number(value);
|
|
191
|
+
if (Number.isFinite(numberValue) && numberValue > 0) {
|
|
192
|
+
return Math.trunc(numberValue);
|
|
193
|
+
}
|
|
194
|
+
return fallback;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const normalizeField = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
198
|
+
const createEndpointSignature = (entry = {}) => {
|
|
199
|
+
const pick = (key) => normalizeField(entry[key]).toLowerCase();
|
|
200
|
+
return ['name', 'model', 'baseUrl', 'key'].map(pick).join('::');
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const normalizeEndpoint = (raw = {}, index = 0) => {
|
|
204
|
+
if (!isPlainObject(raw)) return { endpoint: null };
|
|
205
|
+
|
|
206
|
+
const key = normalizeField(raw.key);
|
|
207
|
+
const baseUrl = normalizeField(raw.baseUrl);
|
|
208
|
+
const model = normalizeField(raw.model);
|
|
209
|
+
if (!baseUrl || !key || !model) {
|
|
210
|
+
return { endpoint: null };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const nameRaw = normalizeField(raw.alias || raw.name);
|
|
214
|
+
const name = nameRaw || model || `model_${index + 1}`;
|
|
215
|
+
const think = raw.think === true;
|
|
216
|
+
const price = Number.isFinite(Number(raw.price)) ? Number(raw.price) : 0;
|
|
217
|
+
const options = isPlainObject(raw.options) ? raw.options : undefined;
|
|
218
|
+
|
|
219
|
+
const endpoint = {
|
|
220
|
+
baseUrl,
|
|
221
|
+
key,
|
|
222
|
+
model,
|
|
223
|
+
name,
|
|
224
|
+
alias: name,
|
|
225
|
+
think,
|
|
226
|
+
price,
|
|
227
|
+
...(options ? { options } : {})
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
endpoint: {
|
|
232
|
+
...endpoint,
|
|
233
|
+
signature: createEndpointSignature({ ...raw, ...endpoint })
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const loadEndpointConfig = () => {
|
|
239
|
+
const ensureResult = ensureConfigFiles();
|
|
240
|
+
const configPath = getConfigPath('chat');
|
|
241
|
+
const parsed = readJsonFile(configPath, { endpoints: [] });
|
|
242
|
+
const rawList = extractEndpointList(parsed);
|
|
243
|
+
|
|
244
|
+
const endpoints = rawList
|
|
245
|
+
.map((entry, idx) => normalizeEndpoint(entry, idx).endpoint)
|
|
246
|
+
.filter(Boolean)
|
|
247
|
+
.map((endpoint, idx) => ({ ...endpoint, name: endpoint.name || endpoint.alias || endpoint.model || `model_${idx + 1}` }));
|
|
248
|
+
|
|
249
|
+
const validEndpoints = endpoints.filter((ep) => ep.key && !ep.key.toLowerCase().includes('your-api-key'));
|
|
250
|
+
if (validEndpoints.length === 0) {
|
|
251
|
+
const suffix = ensureResult.createdFiles.includes(FILE_NAMES.chat)
|
|
252
|
+
? '已自动生成模板,请填写 baseUrl/key/model 后重新运行 mini。'
|
|
253
|
+
: '请在配置中提供至少一个有效的 baseUrl、key、model。';
|
|
254
|
+
throw new ConfigError(`${configPath} 缺少可用的模型配置。${suffix}`, configPath);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { endpoints: validEndpoints, configPath, created: ensureResult.createdFiles.includes(FILE_NAMES.chat) };
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const resolveEndpointCollection = (parsed) => {
|
|
261
|
+
if (Array.isArray(parsed)) {
|
|
262
|
+
return parsed;
|
|
263
|
+
}
|
|
264
|
+
if (parsed && typeof parsed === 'object' && Array.isArray(parsed.endpoints)) {
|
|
265
|
+
return parsed.endpoints;
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const promoteEndpointInConfig = (configPath, signature) => {
|
|
271
|
+
if (!configPath || !signature) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
if (!fs.existsSync(configPath)) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
const rawContent = fs.readFileSync(configPath, 'utf8') || '';
|
|
279
|
+
if (!rawContent.trim()) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const parsed = JSON.parse(rawContent);
|
|
283
|
+
const targetList = resolveEndpointCollection(parsed);
|
|
284
|
+
if (!Array.isArray(targetList) || targetList.length <= 1) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const matchIndex = targetList.findIndex((entry) => createEndpointSignature(entry) === signature);
|
|
288
|
+
if (matchIndex <= 0) {
|
|
289
|
+
return matchIndex === 0;
|
|
290
|
+
}
|
|
291
|
+
const [selected] = targetList.splice(matchIndex, 1);
|
|
292
|
+
targetList.unshift(selected);
|
|
293
|
+
const serialized = Array.isArray(parsed) ? targetList : { ...parsed, endpoints: targetList };
|
|
294
|
+
writeJsonFile(configPath, serialized);
|
|
295
|
+
return true;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const ensureArrayOfStrings = (value, fallback = []) => {
|
|
302
|
+
if (!Array.isArray(value)) {
|
|
303
|
+
return [...fallback];
|
|
304
|
+
}
|
|
305
|
+
return value.map((v) => (typeof v === 'string' ? v.trim() : '')).filter(Boolean);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const getAllMcpServerNames = () => {
|
|
309
|
+
const mcpPath = getConfigPath('mcp');
|
|
310
|
+
const parsed = readJsonFile(mcpPath, { mcpServers: {} });
|
|
311
|
+
const mcpServers = parsed.mcpServers && typeof parsed.mcpServers === 'object'
|
|
312
|
+
? parsed.mcpServers
|
|
313
|
+
: {};
|
|
314
|
+
return Object.keys(mcpServers);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const loadSettings = ({ defaultTools = [] } = {}) => {
|
|
318
|
+
const ensureResult = ensureConfigFiles();
|
|
319
|
+
const settingsPath = getConfigPath('settings');
|
|
320
|
+
const parsed = readJsonFile(settingsPath, DEFAULT_SETTINGS);
|
|
321
|
+
|
|
322
|
+
const settings = {
|
|
323
|
+
mcps: (() => {
|
|
324
|
+
const allMcpNames = getAllMcpServerNames();
|
|
325
|
+
const mcpConfig = parsed.mcpServers;
|
|
326
|
+
|
|
327
|
+
// 只支持对象格式,默认全部启用,只有明确设置为 false 才禁用
|
|
328
|
+
if (!isPlainObject(mcpConfig)) {
|
|
329
|
+
return allMcpNames;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return allMcpNames.filter((name) => mcpConfig[name] !== false);
|
|
333
|
+
})(),
|
|
334
|
+
tools: (() => {
|
|
335
|
+
const toolConfig = parsed.tools;
|
|
336
|
+
|
|
337
|
+
// 只支持对象格式,默认全部启用,只有明确设置为 false 才禁用
|
|
338
|
+
if (!isPlainObject(toolConfig)) {
|
|
339
|
+
return ensureArrayOfStrings(defaultTools);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return defaultTools.filter((name) => toolConfig[name] !== false);
|
|
343
|
+
})(),
|
|
344
|
+
commands: ensureArrayOfStrings(parsed.commands),
|
|
345
|
+
commandPath: detectCommandPath(),
|
|
346
|
+
maxToolTokens: normalizePositiveInteger(
|
|
347
|
+
parsed.maxToolTokens,
|
|
348
|
+
DEFAULT_TOOL_RESPONSE_MAX_TOKENS
|
|
349
|
+
),
|
|
350
|
+
allowedCommands: (() => {
|
|
351
|
+
const list = ensureArrayOfStrings(parsed.allowedCommands);
|
|
352
|
+
return list.length ? Array.from(new Set(list)) : [...DEFAULT_ALLOWED_COMMANDS];
|
|
353
|
+
})(),
|
|
354
|
+
compactTokenThreshold: normalizePositiveInteger(
|
|
355
|
+
parsed.compactTokenThreshold,
|
|
356
|
+
DEFAULT_COMPACT_TOKEN_THRESHOLD
|
|
357
|
+
),
|
|
358
|
+
mcpToolTimeout: normalizePositiveInteger(
|
|
359
|
+
parsed.mcpToolTimeout,
|
|
360
|
+
DEFAULT_MCP_TOOL_TIMEOUT_MS
|
|
361
|
+
),
|
|
362
|
+
outputMaxLength: normalizePositiveInteger(
|
|
363
|
+
parsed.outputMaxLength,
|
|
364
|
+
DEFAULT_OUTPUT_MAX_LENGTH
|
|
365
|
+
),
|
|
366
|
+
executionTimeout: normalizePositiveInteger(
|
|
367
|
+
parsed.executionTimeout,
|
|
368
|
+
DEFAULT_EXECUTION_TIMEOUT
|
|
369
|
+
),
|
|
370
|
+
serviceBootWindow: normalizePositiveInteger(
|
|
371
|
+
parsed.serviceBootWindow,
|
|
372
|
+
DEFAULT_SERVICE_BOOT_WINDOW
|
|
373
|
+
),
|
|
374
|
+
largeFileLineThreshold: normalizePositiveInteger(
|
|
375
|
+
parsed.largeFileLineThreshold,
|
|
376
|
+
DEFAULT_LARGE_FILE_LINE_THRESHOLD
|
|
377
|
+
)
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
settings.toolOutputTokenLimit = settings.maxToolTokens;
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
settingsPath,
|
|
384
|
+
settings,
|
|
385
|
+
created: ensureResult.createdFiles.includes(FILE_NAMES.settings)
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
module.exports = {
|
|
390
|
+
ConfigError,
|
|
391
|
+
loadEndpointConfig,
|
|
392
|
+
getDefaultConfigPath,
|
|
393
|
+
promoteEndpointInConfig,
|
|
394
|
+
loadSettings,
|
|
395
|
+
ensureConfigFiles,
|
|
396
|
+
getConfigPath,
|
|
397
|
+
DEFAULT_ALLOWED_COMMANDS,
|
|
398
|
+
DEFAULT_TOOL_RESPONSE_MAX_TOKENS,
|
|
399
|
+
DEFAULT_COMPACT_TOKEN_THRESHOLD,
|
|
400
|
+
COMPACT_SUMMARY_PROMPT
|
|
401
|
+
};
|
package/src/tools/bash.js
CHANGED
|
@@ -2,10 +2,6 @@ const { spawn } = require('child_process');
|
|
|
2
2
|
const { resolveWorkspacePath } = require('../utils/helpers');
|
|
3
3
|
const { DEFAULT_ALLOWED_COMMANDS } = require('../config');
|
|
4
4
|
|
|
5
|
-
const OUTPUT_MAX_LENGTH = 12000;
|
|
6
|
-
const EXECUTION_TIMEOUT = 300000;
|
|
7
|
-
const SERVICE_RETURN_DELAY = 5000;
|
|
8
|
-
|
|
9
5
|
// Git 只读命令白名单
|
|
10
6
|
const GIT_READONLY_COMMANDS = ['show', 'diff', 'log', 'status', 'branch', 'tag', 'ls-files', 'ls-tree', 'rev-parse', 'reflog', 'blame', 'shortlog', 'describe', 'config --get', 'config --list', 'remote', 'ls-remote', 'fetch --dry-run', 'grep'];
|
|
11
7
|
|
|
@@ -86,6 +82,10 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
|
|
|
86
82
|
const allowedCommands = Array.isArray(context.allowedCommands) && context.allowedCommands.length > 0
|
|
87
83
|
? context.allowedCommands
|
|
88
84
|
: DEFAULT_ALLOWED_COMMANDS;
|
|
85
|
+
|
|
86
|
+
const outputMaxLength = context.outputMaxLength || 12000;
|
|
87
|
+
const executionTimeout = context.executionTimeout || 300000;
|
|
88
|
+
const serviceBootWindow = context.serviceBootWindow || 5000;
|
|
89
89
|
const commands = splitShellCommands(normalizedCommand);
|
|
90
90
|
if (commands.length === 0) {
|
|
91
91
|
return '未找到有效的命令';
|
|
@@ -129,15 +129,15 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
|
|
|
129
129
|
|
|
130
130
|
child.stdout.on('data', (data) => {
|
|
131
131
|
stdout += data.toString();
|
|
132
|
-
if (stdout.length >
|
|
133
|
-
stdout = stdout.slice(0,
|
|
132
|
+
if (stdout.length > outputMaxLength) {
|
|
133
|
+
stdout = stdout.slice(0, outputMaxLength);
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
child.stderr.on('data', (data) => {
|
|
138
138
|
stderr += data.toString();
|
|
139
|
-
if (stderr.length >
|
|
140
|
-
stderr = stderr.slice(0,
|
|
139
|
+
if (stderr.length > outputMaxLength) {
|
|
140
|
+
stderr = stderr.slice(0, outputMaxLength);
|
|
141
141
|
}
|
|
142
142
|
});
|
|
143
143
|
|
|
@@ -169,8 +169,8 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
|
|
|
169
169
|
const stdOutput = stdout.trim();
|
|
170
170
|
const errOutput = stderr.trim();
|
|
171
171
|
const captured = stdOutput || errOutput ? `\n当前输出:\n${stdOutput}${errOutput ? `\n错误:\n${errOutput}` : ''}` : '\n暂无输出';
|
|
172
|
-
resolve(`已等待 ${
|
|
173
|
-
},
|
|
172
|
+
resolve(`已等待 ${serviceBootWindow / 1000}s,命令已在后台持续运行(PID: ${child.pid})。${captured}`);
|
|
173
|
+
}, serviceBootWindow);
|
|
174
174
|
|
|
175
175
|
const serviceErrorHandler = (error) => {
|
|
176
176
|
clearTimeout(timer);
|
|
@@ -190,8 +190,8 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
|
|
|
190
190
|
if (settled) return;
|
|
191
191
|
child.kill('SIGTERM');
|
|
192
192
|
cleanup();
|
|
193
|
-
resolve(
|
|
194
|
-
},
|
|
193
|
+
resolve(`命令执行超时 (超过 ${executionTimeout / 1000}s)`);
|
|
194
|
+
}, executionTimeout);
|
|
195
195
|
}
|
|
196
196
|
});
|
|
197
197
|
};
|
package/src/tools/index.js
CHANGED
|
@@ -15,7 +15,15 @@ const createToolRuntime = async (workspaceRoot, options = {}) => {
|
|
|
15
15
|
const enabledMcps = Array.isArray(settings.mcps) ? settings.mcps : [];
|
|
16
16
|
const allowedCommands = settings.allowedCommands;
|
|
17
17
|
|
|
18
|
-
const context = {
|
|
18
|
+
const context = {
|
|
19
|
+
workspaceRoot,
|
|
20
|
+
allowedCommands,
|
|
21
|
+
outputMaxLength: settings.outputMaxLength,
|
|
22
|
+
executionTimeout: settings.executionTimeout,
|
|
23
|
+
serviceBootWindow: settings.serviceBootWindow,
|
|
24
|
+
largeFileLineThreshold: settings.largeFileLineThreshold,
|
|
25
|
+
...options
|
|
26
|
+
};
|
|
19
27
|
|
|
20
28
|
const tools = [];
|
|
21
29
|
const handlers = {};
|
|
@@ -35,7 +43,9 @@ const createToolRuntime = async (workspaceRoot, options = {}) => {
|
|
|
35
43
|
|
|
36
44
|
TOOL_MODULES.forEach(registerTool);
|
|
37
45
|
|
|
38
|
-
const mcpManager = await createMcpManager(workspaceRoot, enabledMcps
|
|
46
|
+
const mcpManager = await createMcpManager(workspaceRoot, enabledMcps, {
|
|
47
|
+
mcpToolTimeout: settings.mcpToolTimeout
|
|
48
|
+
});
|
|
39
49
|
let mcpConfigPath = null;
|
|
40
50
|
const mcpToolNames = new Set(); // 记录所有 MCP 工具名称
|
|
41
51
|
const enabledMcpNames = []; // 记录启用的 MCP 服务器名称
|
package/src/tools/mcp.js
CHANGED
|
@@ -305,9 +305,10 @@ const formatMcpContent = (content) => {
|
|
|
305
305
|
};
|
|
306
306
|
|
|
307
307
|
class McpManager {
|
|
308
|
-
constructor(workspaceRoot, allowedMcpNames = null) {
|
|
308
|
+
constructor(workspaceRoot, allowedMcpNames = null, options = {}) {
|
|
309
309
|
this.workspaceRoot = workspaceRoot;
|
|
310
310
|
this.allowedMcpNames = Array.isArray(allowedMcpNames) ? allowedMcpNames : null;
|
|
311
|
+
this.mcpToolTimeout = options.mcpToolTimeout || 600000;
|
|
311
312
|
this.clients = [];
|
|
312
313
|
}
|
|
313
314
|
|
|
@@ -407,7 +408,7 @@ class McpManager {
|
|
|
407
408
|
},
|
|
408
409
|
undefined,
|
|
409
410
|
{
|
|
410
|
-
timeout:
|
|
411
|
+
timeout: this.mcpToolTimeout
|
|
411
412
|
}
|
|
412
413
|
);
|
|
413
414
|
const isError = Boolean(result && result.isError);
|
|
@@ -460,11 +461,11 @@ class McpManager {
|
|
|
460
461
|
}
|
|
461
462
|
}
|
|
462
463
|
|
|
463
|
-
const createMcpManager = async (workspaceRoot, allowedMcpNames = null) => {
|
|
464
|
+
const createMcpManager = async (workspaceRoot, allowedMcpNames = null, options = {}) => {
|
|
464
465
|
if (Array.isArray(allowedMcpNames) && allowedMcpNames.length === 0) {
|
|
465
466
|
return null;
|
|
466
467
|
}
|
|
467
|
-
const manager = new McpManager(workspaceRoot, allowedMcpNames);
|
|
468
|
+
const manager = new McpManager(workspaceRoot, allowedMcpNames, options);
|
|
468
469
|
await manager.initialize();
|
|
469
470
|
const tools = manager.getTools();
|
|
470
471
|
if (!tools || tools.length === 0) {
|