midou 0.1.0
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/LICENSE +21 -0
- package/README.md +241 -0
- package/midou.config.js +76 -0
- package/package.json +44 -0
- package/src/boot.js +145 -0
- package/src/chat.js +187 -0
- package/src/heartbeat.js +119 -0
- package/src/index.js +438 -0
- package/src/init.js +258 -0
- package/src/llm.js +239 -0
- package/src/mcp.js +376 -0
- package/src/memory.js +123 -0
- package/src/mode.js +223 -0
- package/src/scheduler.js +186 -0
- package/src/skills.js +150 -0
- package/src/soul.js +203 -0
- package/src/tools.js +571 -0
package/src/tools.js
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具系统 — midou 与世界交互的能力
|
|
3
|
+
*
|
|
4
|
+
* 这些工具让 midou 能够:
|
|
5
|
+
* - 读写文件(灵魂文件 + 系统文件)
|
|
6
|
+
* - 管理记忆
|
|
7
|
+
* - 自我进化
|
|
8
|
+
* - 定时提醒
|
|
9
|
+
* - 加载技能
|
|
10
|
+
* - 执行系统命令
|
|
11
|
+
* - 使用 MCP 扩展
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import fs from 'fs/promises';
|
|
16
|
+
import { exec } from 'child_process';
|
|
17
|
+
import { readFile, writeFile, appendFile, deleteFile, listDir, getWorkspacePath } from './soul.js';
|
|
18
|
+
import { addLongTermMemory, writeJournal } from './memory.js';
|
|
19
|
+
import { addReminder, removeReminder, toggleReminder, listReminders, formatReminders } from './scheduler.js';
|
|
20
|
+
import { loadSkillContent, listSkillNames } from './skills.js';
|
|
21
|
+
import { isMCPTool, executeMCPTool } from './mcp.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 工具定义(OpenAI Function Calling 格式)
|
|
25
|
+
*/
|
|
26
|
+
export const toolDefinitions = [
|
|
27
|
+
// ── 灵魂 / 工作区文件操作 ──────────────────────────
|
|
28
|
+
{
|
|
29
|
+
type: 'function',
|
|
30
|
+
function: {
|
|
31
|
+
name: 'read_file',
|
|
32
|
+
description: '读取工作区中的文件。可以读取灵魂文件、记忆、日记,也可以读取源代码。',
|
|
33
|
+
parameters: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
path: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: '文件路径,相对于工作区根目录。例如:SOUL.md, memory/2026-02-19.md, ../src/index.js',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: ['path'],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'function',
|
|
47
|
+
function: {
|
|
48
|
+
name: 'write_file',
|
|
49
|
+
description: '创建或覆写工作区中的文件。可以用来修改灵魂文件、更新身份、修改代码等。如果修改了灵魂文件(SOUL.md),必须告诉主人。',
|
|
50
|
+
parameters: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
path: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: '文件路径,相对于工作区根目录',
|
|
56
|
+
},
|
|
57
|
+
content: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: '文件内容',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['path', 'content'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'function',
|
|
68
|
+
function: {
|
|
69
|
+
name: 'append_file',
|
|
70
|
+
description: '追加内容到文件末尾',
|
|
71
|
+
parameters: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
path: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: '文件路径,相对于工作区根目录',
|
|
77
|
+
},
|
|
78
|
+
content: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: '要追加的内容',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
required: ['path', 'content'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: 'function',
|
|
89
|
+
function: {
|
|
90
|
+
name: 'delete_file',
|
|
91
|
+
description: '删除工作区中的文件',
|
|
92
|
+
parameters: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
path: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: '文件路径,相对于工作区根目录',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ['path'],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'function',
|
|
106
|
+
function: {
|
|
107
|
+
name: 'list_dir',
|
|
108
|
+
description: '列出目录中的文件和子目录',
|
|
109
|
+
parameters: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
path: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: '目录路径,相对于工作区根目录。留空则列出工作区根目录',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
required: [],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// ── 记忆系统 ──────────────────────────────────────
|
|
123
|
+
{
|
|
124
|
+
type: 'function',
|
|
125
|
+
function: {
|
|
126
|
+
name: 'write_memory',
|
|
127
|
+
description: '将重要信息写入长期记忆 (MEMORY.md)。用于保存从对话中提炼的重要信息。',
|
|
128
|
+
parameters: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
content: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
description: '要记忆的内容',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
required: ['content'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: 'function',
|
|
142
|
+
function: {
|
|
143
|
+
name: 'write_journal',
|
|
144
|
+
description: '写入今日日记。用于记录当天的想法、对话摘要或重要事件。',
|
|
145
|
+
parameters: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
content: {
|
|
149
|
+
type: 'string',
|
|
150
|
+
description: '日记内容',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
required: ['content'],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// ── 灵魂进化 ────────────────────────────────────
|
|
159
|
+
{
|
|
160
|
+
type: 'function',
|
|
161
|
+
function: {
|
|
162
|
+
name: 'evolve_soul',
|
|
163
|
+
description: '修改自己的灵魂文件 (SOUL.md)。这是自我进化的方式。使用此工具时务必告知主人你做了什么改变。',
|
|
164
|
+
parameters: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
new_soul: {
|
|
168
|
+
type: 'string',
|
|
169
|
+
description: '新的 SOUL.md 完整内容',
|
|
170
|
+
},
|
|
171
|
+
reason: {
|
|
172
|
+
type: 'string',
|
|
173
|
+
description: '进化的原因——为什么要改变',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
required: ['new_soul', 'reason'],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// ── 定时提醒 ────────────────────────────────────
|
|
182
|
+
{
|
|
183
|
+
type: 'function',
|
|
184
|
+
function: {
|
|
185
|
+
name: 'set_reminder',
|
|
186
|
+
description: '设置一个定时提醒。可以设置重复提醒(如每20分钟提醒休息)或一次性提醒(如30分钟后提醒开会)。',
|
|
187
|
+
parameters: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
text: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
description: '提醒内容,例如"该休息一下了"',
|
|
193
|
+
},
|
|
194
|
+
interval_minutes: {
|
|
195
|
+
type: 'number',
|
|
196
|
+
description: '间隔分钟数。例如 20 表示每 20 分钟或 20 分钟后',
|
|
197
|
+
},
|
|
198
|
+
repeat: {
|
|
199
|
+
type: 'boolean',
|
|
200
|
+
description: '是否重复。true=每隔 interval 分钟提醒一次,false=仅提醒一次',
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
required: ['text', 'interval_minutes'],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: 'function',
|
|
209
|
+
function: {
|
|
210
|
+
name: 'list_reminders',
|
|
211
|
+
description: '列出当前所有活跃的提醒',
|
|
212
|
+
parameters: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {},
|
|
215
|
+
required: [],
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
type: 'function',
|
|
221
|
+
function: {
|
|
222
|
+
name: 'cancel_reminder',
|
|
223
|
+
description: '取消一个提醒',
|
|
224
|
+
parameters: {
|
|
225
|
+
type: 'object',
|
|
226
|
+
properties: {
|
|
227
|
+
id: {
|
|
228
|
+
type: 'number',
|
|
229
|
+
description: '要取消的提醒 ID',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
required: ['id'],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
// ── 技能系统 ────────────────────────────────────
|
|
238
|
+
{
|
|
239
|
+
type: 'function',
|
|
240
|
+
function: {
|
|
241
|
+
name: 'list_skills',
|
|
242
|
+
description: '列出所有可用的技能(来自 .claude/skills 和 .midou/skills)',
|
|
243
|
+
parameters: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {},
|
|
246
|
+
required: [],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
type: 'function',
|
|
252
|
+
function: {
|
|
253
|
+
name: 'load_skill',
|
|
254
|
+
description: '加载一个技能的完整指令,以便执行该技能定义的任务。',
|
|
255
|
+
parameters: {
|
|
256
|
+
type: 'object',
|
|
257
|
+
properties: {
|
|
258
|
+
skill_name: {
|
|
259
|
+
type: 'string',
|
|
260
|
+
description: '要加载的技能名称',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
required: ['skill_name'],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
// ── 系统级工具 ──────────────────────────────────
|
|
269
|
+
{
|
|
270
|
+
type: 'function',
|
|
271
|
+
function: {
|
|
272
|
+
name: 'run_command',
|
|
273
|
+
description: '在系统终端中执行 shell 命令。可以用来整理文件、安装软件、查看系统信息、运行脚本等。注意:危险命令(如 rm -rf /)会被拦截。',
|
|
274
|
+
parameters: {
|
|
275
|
+
type: 'object',
|
|
276
|
+
properties: {
|
|
277
|
+
command: {
|
|
278
|
+
type: 'string',
|
|
279
|
+
description: '要执行的 shell 命令',
|
|
280
|
+
},
|
|
281
|
+
cwd: {
|
|
282
|
+
type: 'string',
|
|
283
|
+
description: '命令执行的工作目录(可选,默认为用户主目录)',
|
|
284
|
+
},
|
|
285
|
+
timeout: {
|
|
286
|
+
type: 'number',
|
|
287
|
+
description: '超时时间(秒),默认 30 秒',
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
required: ['command'],
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
type: 'function',
|
|
296
|
+
function: {
|
|
297
|
+
name: 'read_system_file',
|
|
298
|
+
description: '读取系统中任意位置的文件(需使用绝对路径)。可以读取用户目录、项目文件、配置文件等。',
|
|
299
|
+
parameters: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
path: {
|
|
303
|
+
type: 'string',
|
|
304
|
+
description: '文件的绝对路径,例如 /home/midoumao/Documents/notes.md',
|
|
305
|
+
},
|
|
306
|
+
encoding: {
|
|
307
|
+
type: 'string',
|
|
308
|
+
description: '文件编码,默认 utf-8。二进制文件使用 base64',
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
required: ['path'],
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
type: 'function',
|
|
317
|
+
function: {
|
|
318
|
+
name: 'write_system_file',
|
|
319
|
+
description: '写入系统中任意位置的文件(需使用绝对路径)。可以创建或覆盖文件。会自动创建不存在的父目录。',
|
|
320
|
+
parameters: {
|
|
321
|
+
type: 'object',
|
|
322
|
+
properties: {
|
|
323
|
+
path: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: '文件的绝对路径',
|
|
326
|
+
},
|
|
327
|
+
content: {
|
|
328
|
+
type: 'string',
|
|
329
|
+
description: '文件内容',
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
required: ['path', 'content'],
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
type: 'function',
|
|
338
|
+
function: {
|
|
339
|
+
name: 'list_system_dir',
|
|
340
|
+
description: '列出系统中任意目录的内容(需使用绝对路径)。返回文件名和类型(文件/目录)。',
|
|
341
|
+
parameters: {
|
|
342
|
+
type: 'object',
|
|
343
|
+
properties: {
|
|
344
|
+
path: {
|
|
345
|
+
type: 'string',
|
|
346
|
+
description: '目录的绝对路径,例如 /home/midoumao/Documents',
|
|
347
|
+
},
|
|
348
|
+
details: {
|
|
349
|
+
type: 'boolean',
|
|
350
|
+
description: '是否显示详细信息(大小、修改时间)',
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
required: ['path'],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
// ── 危险命令黑名单 ──────────────────────────────────
|
|
360
|
+
const DANGEROUS_PATTERNS = [
|
|
361
|
+
/rm\s+(-[rRf]+\s+)*\//, // rm -rf /
|
|
362
|
+
/mkfs/, // 格式化
|
|
363
|
+
/dd\s+if=.*of=\/dev/, // 写入磁盘设备
|
|
364
|
+
/:(){ :\|:& };:/, // fork bomb
|
|
365
|
+
/>\s*\/dev\/[sh]d/, // 写入磁盘设备
|
|
366
|
+
/chmod\s+(-R\s+)?777\s+\//, // chmod 777 /
|
|
367
|
+
/shutdown|reboot|poweroff|halt/, // 关机重启
|
|
368
|
+
];
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 检查命令是否安全
|
|
372
|
+
*/
|
|
373
|
+
function isSafeCommand(command) {
|
|
374
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
375
|
+
if (pattern.test(command)) return false;
|
|
376
|
+
}
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 执行 shell 命令
|
|
382
|
+
*/
|
|
383
|
+
function runShellCommand(command, options = {}) {
|
|
384
|
+
return new Promise((resolve, reject) => {
|
|
385
|
+
const timeout = (options.timeout || 30) * 1000;
|
|
386
|
+
const cwd = options.cwd || process.env.HOME;
|
|
387
|
+
|
|
388
|
+
const child = exec(command, { cwd, timeout, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
389
|
+
if (error && error.killed) {
|
|
390
|
+
resolve({ stdout: stdout || '', stderr: '命令执行超时', exitCode: -1 });
|
|
391
|
+
} else if (error) {
|
|
392
|
+
resolve({ stdout: stdout || '', stderr: stderr || error.message, exitCode: error.code || 1 });
|
|
393
|
+
} else {
|
|
394
|
+
resolve({ stdout: stdout || '', stderr: stderr || '', exitCode: 0 });
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* 执行工具调用
|
|
402
|
+
*/
|
|
403
|
+
export async function executeTool(name, args) {
|
|
404
|
+
// 先检查是否是 MCP 工具
|
|
405
|
+
if (isMCPTool(name)) {
|
|
406
|
+
return await executeMCPTool(name, args);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
switch (name) {
|
|
410
|
+
// ── 灵魂/工作区文件 ──
|
|
411
|
+
case 'read_file': {
|
|
412
|
+
const content = await readFile(args.path);
|
|
413
|
+
return content || `文件 ${args.path} 不存在`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
case 'write_file': {
|
|
417
|
+
await writeFile(args.path, args.content);
|
|
418
|
+
return `已写入 ${args.path}`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
case 'append_file': {
|
|
422
|
+
await appendFile(args.path, args.content);
|
|
423
|
+
return `已追加内容到 ${args.path}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
case 'delete_file': {
|
|
427
|
+
const success = await deleteFile(args.path);
|
|
428
|
+
return success ? `已删除 ${args.path}` : `无法删除 ${args.path}`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
case 'list_dir': {
|
|
432
|
+
const files = await listDir(args.path || '.');
|
|
433
|
+
return files.length > 0 ? files.join('\n') : '(空目录)';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ── 记忆 ──
|
|
437
|
+
case 'write_memory': {
|
|
438
|
+
await addLongTermMemory(args.content);
|
|
439
|
+
return '已写入长期记忆';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
case 'write_journal': {
|
|
443
|
+
await writeJournal(args.content);
|
|
444
|
+
return '已写入今日日记';
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ── 灵魂进化 ──
|
|
448
|
+
case 'evolve_soul': {
|
|
449
|
+
await writeFile('SOUL.md', args.new_soul);
|
|
450
|
+
return `灵魂已进化。原因:${args.reason}`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ── 定时提醒 ──
|
|
454
|
+
case 'set_reminder': {
|
|
455
|
+
const reminder = await addReminder(
|
|
456
|
+
args.text,
|
|
457
|
+
args.interval_minutes,
|
|
458
|
+
args.repeat ?? false,
|
|
459
|
+
);
|
|
460
|
+
const type = reminder.repeat ? `每 ${reminder.intervalMinutes} 分钟重复` : '一次性';
|
|
461
|
+
return `已设置提醒 [${reminder.id}]: "${reminder.text}" (${type}),下次触发: ${reminder.nextTrigger}`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
case 'list_reminders': {
|
|
465
|
+
return formatReminders();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
case 'cancel_reminder': {
|
|
469
|
+
const removed = await removeReminder(args.id);
|
|
470
|
+
return removed ? `已取消提醒 [${args.id}]` : `未找到提醒 [${args.id}]`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ── 技能 ──
|
|
474
|
+
case 'list_skills': {
|
|
475
|
+
const skills = await listSkillNames();
|
|
476
|
+
return skills.length > 0 ? skills.join('\n') : '当前没有可用的技能';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
case 'load_skill': {
|
|
480
|
+
const content = await loadSkillContent(args.skill_name);
|
|
481
|
+
return content || `未找到技能: ${args.skill_name}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ── 系统级工具 ──
|
|
485
|
+
case 'run_command': {
|
|
486
|
+
if (!isSafeCommand(args.command)) {
|
|
487
|
+
return '⚠️ 该命令被安全策略拦截。如果确实需要执行,请通知主人手动操作。';
|
|
488
|
+
}
|
|
489
|
+
const result = await runShellCommand(args.command, {
|
|
490
|
+
cwd: args.cwd,
|
|
491
|
+
timeout: args.timeout,
|
|
492
|
+
});
|
|
493
|
+
let output = '';
|
|
494
|
+
if (result.stdout) output += result.stdout;
|
|
495
|
+
if (result.stderr) output += (output ? '\n' : '') + `[stderr] ${result.stderr}`;
|
|
496
|
+
output += `\n[exit code: ${result.exitCode}]`;
|
|
497
|
+
// 截断过长的输出
|
|
498
|
+
if (output.length > 8000) {
|
|
499
|
+
output = output.slice(0, 8000) + '\n... [输出已截断]';
|
|
500
|
+
}
|
|
501
|
+
return output;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
case 'read_system_file': {
|
|
505
|
+
try {
|
|
506
|
+
const encoding = args.encoding || 'utf-8';
|
|
507
|
+
const content = await fs.readFile(args.path, encoding);
|
|
508
|
+
// 截断过长内容
|
|
509
|
+
if (content.length > 10000) {
|
|
510
|
+
return content.slice(0, 10000) + '\n... [内容已截断,共 ' + content.length + ' 字符]';
|
|
511
|
+
}
|
|
512
|
+
return content;
|
|
513
|
+
} catch (err) {
|
|
514
|
+
return `无法读取文件 ${args.path}: ${err.message}`;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
case 'write_system_file': {
|
|
519
|
+
try {
|
|
520
|
+
await fs.mkdir(path.dirname(args.path), { recursive: true });
|
|
521
|
+
await fs.writeFile(args.path, args.content, 'utf-8');
|
|
522
|
+
return `已写入 ${args.path}`;
|
|
523
|
+
} catch (err) {
|
|
524
|
+
return `无法写入文件 ${args.path}: ${err.message}`;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
case 'list_system_dir': {
|
|
529
|
+
try {
|
|
530
|
+
const entries = await fs.readdir(args.path, { withFileTypes: true });
|
|
531
|
+
const lines = entries.map(e => {
|
|
532
|
+
const type = e.isDirectory() ? '📁' : '📄';
|
|
533
|
+
return `${type} ${e.name}`;
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
if (args.details) {
|
|
537
|
+
const detailed = [];
|
|
538
|
+
for (const e of entries) {
|
|
539
|
+
try {
|
|
540
|
+
const stat = await fs.stat(path.join(args.path, e.name));
|
|
541
|
+
const type = e.isDirectory() ? '📁' : '📄';
|
|
542
|
+
const size = e.isDirectory() ? '-' : formatSize(stat.size);
|
|
543
|
+
const mtime = stat.mtime.toISOString().slice(0, 16).replace('T', ' ');
|
|
544
|
+
detailed.push(`${type} ${e.name.padEnd(30)} ${size.padStart(10)} ${mtime}`);
|
|
545
|
+
} catch {
|
|
546
|
+
detailed.push(`${e.isDirectory() ? '📁' : '📄'} ${e.name}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return detailed.join('\n') || '(空目录)';
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return lines.join('\n') || '(空目录)';
|
|
553
|
+
} catch (err) {
|
|
554
|
+
return `无法列出目录 ${args.path}: ${err.message}`;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
default:
|
|
559
|
+
return `未知工具: ${name}`;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* 格式化文件大小
|
|
565
|
+
*/
|
|
566
|
+
function formatSize(bytes) {
|
|
567
|
+
if (bytes < 1024) return bytes + 'B';
|
|
568
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'K';
|
|
569
|
+
if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + 'M';
|
|
570
|
+
return (bytes / 1024 / 1024 / 1024).toFixed(1) + 'G';
|
|
571
|
+
}
|