foliko 1.1.93 → 2.0.1
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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +224 -0
- package/plugins/core/default/config.js +222 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +3 -3
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +4 -4
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +6 -17
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +4 -4
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +16 -16
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +484 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{core → common}/constants.js +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* 提供文件系统操作的常用工具
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { Plugin } = require('
|
|
6
|
+
const { Plugin } = require('../../../src/plugin/base')
|
|
7
7
|
const { NodeHtmlMarkdown } = require('node-html-markdown')
|
|
8
8
|
const { Readable } = require('stream')
|
|
9
9
|
const { pipeline } = require('stream/promises')
|
|
10
|
+
|
|
10
11
|
class FileSystemPlugin extends Plugin {
|
|
11
12
|
constructor(config = {}) {
|
|
12
13
|
super()
|
|
@@ -14,7 +15,7 @@ class FileSystemPlugin extends Plugin {
|
|
|
14
15
|
this.version = '1.0.0'
|
|
15
16
|
this.description = '文件系统工具插件'
|
|
16
17
|
this.priority = 12
|
|
17
|
-
this.system = true
|
|
18
|
+
this.system = true
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
install(framework) {
|
|
@@ -23,17 +24,14 @@ class FileSystemPlugin extends Plugin {
|
|
|
23
24
|
const path = require('path')
|
|
24
25
|
const { exec } = require('child_process')
|
|
25
26
|
|
|
26
|
-
// 路径安全验证:防止路径穿越攻击
|
|
27
27
|
const validatePath = (filePath, allowOutsideCwd = false) => {
|
|
28
28
|
const resolved = path.resolve(filePath)
|
|
29
29
|
const cwd = framework?.getCwd?.() ?? process.cwd()
|
|
30
30
|
|
|
31
|
-
// 允许绝对路径且在允许列表中的路径(如果有的话)
|
|
32
31
|
if (allowOutsideCwd) {
|
|
33
32
|
return { valid: true, resolved }
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
// 检查是否在 cwd 目录下
|
|
37
35
|
if (!resolved.startsWith(cwd)) {
|
|
38
36
|
return { valid: false, error: `路径不允许访问: ${filePath}` }
|
|
39
37
|
}
|
|
@@ -41,7 +39,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
41
39
|
return { valid: true, resolved }
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
// 读取目录
|
|
45
42
|
framework.registerTool({
|
|
46
43
|
name: 'read_directory',
|
|
47
44
|
description: '读取目录内容,列出目录中的文件和子目录',
|
|
@@ -84,7 +81,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
84
81
|
}
|
|
85
82
|
})
|
|
86
83
|
|
|
87
|
-
// 创建目录
|
|
88
84
|
framework.registerTool({
|
|
89
85
|
name: 'create_directory',
|
|
90
86
|
description: '创建新目录',
|
|
@@ -103,7 +99,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
103
99
|
}
|
|
104
100
|
})
|
|
105
101
|
|
|
106
|
-
// 读取文件
|
|
107
102
|
framework.registerTool({
|
|
108
103
|
name: 'read',
|
|
109
104
|
description: '读取文件内容。path 是必填参数。',
|
|
@@ -117,7 +112,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
117
112
|
return { success: false, error: 'path 是必填参数' }
|
|
118
113
|
}
|
|
119
114
|
|
|
120
|
-
// 路径安全验证
|
|
121
115
|
const pathCheck = validatePath(filePath)
|
|
122
116
|
if (!pathCheck.valid) {
|
|
123
117
|
return { success: false, error: pathCheck.error }
|
|
@@ -154,7 +148,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
154
148
|
}
|
|
155
149
|
})
|
|
156
150
|
|
|
157
|
-
// 写入文件
|
|
158
151
|
framework.registerTool({
|
|
159
152
|
name: 'write',
|
|
160
153
|
description: `创建或写入文件内容(仅适用于小文件,建议小于10KB)。
|
|
@@ -168,7 +161,7 @@ class FileSystemPlugin extends Plugin {
|
|
|
168
161
|
}),
|
|
169
162
|
execute: async (args, framework) => {
|
|
170
163
|
const { path: filePath, content, mode = 'write' } = args
|
|
171
|
-
|
|
164
|
+
|
|
172
165
|
const pathCheck = validatePath(filePath)
|
|
173
166
|
if (!pathCheck.valid) {
|
|
174
167
|
return { success: false, error: pathCheck.error }
|
|
@@ -179,35 +172,33 @@ class FileSystemPlugin extends Plugin {
|
|
|
179
172
|
if (!fs.existsSync(dir)) {
|
|
180
173
|
fs.mkdirSync(dir, { recursive: true })
|
|
181
174
|
}
|
|
182
|
-
|
|
175
|
+
|
|
183
176
|
const flag = mode === 'append' ? 'a' : 'w'
|
|
184
|
-
const writeStream = fs.createWriteStream(pathCheck.resolved, {
|
|
177
|
+
const writeStream = fs.createWriteStream(pathCheck.resolved, {
|
|
185
178
|
encoding: 'utf8',
|
|
186
179
|
flags: flag,
|
|
187
|
-
highWaterMark: 256 * 1024
|
|
180
|
+
highWaterMark: 256 * 1024
|
|
188
181
|
})
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const CHUNK_SIZE = 1024 * 1024 // 1MB 块
|
|
182
|
+
|
|
183
|
+
const CHUNK_SIZE = 1024 * 1024
|
|
192
184
|
let offset = 0
|
|
193
|
-
|
|
185
|
+
|
|
194
186
|
const writeChunk = async () => {
|
|
195
187
|
while (offset < content.length) {
|
|
196
188
|
const end = Math.min(offset + CHUNK_SIZE, content.length)
|
|
197
189
|
const chunk = content.slice(offset, end)
|
|
198
|
-
|
|
190
|
+
|
|
199
191
|
const canContinue = writeStream.write(chunk)
|
|
200
192
|
offset = end
|
|
201
|
-
|
|
202
|
-
// 如果缓冲区满了,等待 drain 事件
|
|
193
|
+
|
|
203
194
|
if (!canContinue && offset < content.length) {
|
|
204
195
|
await new Promise(resolve => writeStream.once('drain', resolve))
|
|
205
196
|
}
|
|
206
197
|
}
|
|
207
198
|
}
|
|
208
|
-
|
|
199
|
+
|
|
209
200
|
await writeChunk()
|
|
210
|
-
|
|
201
|
+
|
|
211
202
|
return new Promise((resolve, reject) => {
|
|
212
203
|
writeStream.end((err) => {
|
|
213
204
|
if (err) reject(err)
|
|
@@ -222,22 +213,16 @@ class FileSystemPlugin extends Plugin {
|
|
|
222
213
|
})
|
|
223
214
|
})
|
|
224
215
|
})
|
|
225
|
-
|
|
216
|
+
|
|
226
217
|
} catch (error) {
|
|
227
218
|
return { success: false, error: error.message }
|
|
228
219
|
}
|
|
229
220
|
}
|
|
230
221
|
})
|
|
231
222
|
|
|
232
|
-
// 追加文件内容(用于大文件分段写入)
|
|
233
223
|
framework.registerTool({
|
|
234
224
|
name: 'append_to_file',
|
|
235
|
-
description: `追加内容到文件末尾。适用于大文件写入,每次最多追加约64KB
|
|
236
|
-
|
|
237
|
-
使用方式:
|
|
238
|
-
1. 先用 write_file 创建新文件(或清空现有文件)
|
|
239
|
-
2. 多次调用 append_to_file 追加内容,每次 content 不超过 64KB
|
|
240
|
-
3. 追加顺序很重要,content 应该按文件顺序排列`,
|
|
225
|
+
description: `追加内容到文件末尾。适用于大文件写入,每次最多追加约64KB内容。`,
|
|
241
226
|
inputSchema: z.object({
|
|
242
227
|
path: z.string().describe('文件路径'),
|
|
243
228
|
content: z.string().describe('要追加的内容(建议不超过64KB)')
|
|
@@ -251,7 +236,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
251
236
|
}
|
|
252
237
|
|
|
253
238
|
try {
|
|
254
|
-
// 如果文件不存在,返回错误(应该先用 write_file 创建)
|
|
255
239
|
if (!fs.existsSync(pathCheck.resolved)) {
|
|
256
240
|
return { success: false, error: '文件不存在,请先用 write_file 创建文件' }
|
|
257
241
|
}
|
|
@@ -271,7 +255,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
271
255
|
}
|
|
272
256
|
})
|
|
273
257
|
|
|
274
|
-
// 删除文件
|
|
275
258
|
framework.registerTool({
|
|
276
259
|
name: 'delete_file',
|
|
277
260
|
description: '删除文件或目录',
|
|
@@ -284,7 +267,6 @@ class FileSystemPlugin extends Plugin {
|
|
|
284
267
|
const targetPath = args.path || args.targetPath
|
|
285
268
|
const recursive = args.recursive || false
|
|
286
269
|
|
|
287
|
-
// 路径安全验证
|
|
288
270
|
const pathCheck = validatePath(targetPath)
|
|
289
271
|
if (!pathCheck.valid) {
|
|
290
272
|
return { success: false, error: pathCheck.error }
|
|
@@ -311,162 +293,9 @@ class FileSystemPlugin extends Plugin {
|
|
|
311
293
|
}
|
|
312
294
|
})
|
|
313
295
|
|
|
314
|
-
// 修改文件(替换文本)
|
|
315
|
-
// framework.registerTool({
|
|
316
|
-
// name: 'modify_file',
|
|
317
|
-
// description: '(已弃用,请使用 edit 替代)修改文件内容(替换文本),支持精确匹配和正则',
|
|
318
|
-
// inputSchema: z.object({
|
|
319
|
-
// filePath: z.string().describe('文件路径(必须)'),
|
|
320
|
-
// find: z.string().min(1).describe('要查找的文本(必填,不能为空)'),
|
|
321
|
-
// replace: z.string().describe('替换后的文本'),
|
|
322
|
-
// replaceAll: z.boolean().optional().describe('是否替换所有匹配,默认 true'),
|
|
323
|
-
// useRegex: z.boolean().optional().describe('是否使用正则表达式匹配,默认 false'),
|
|
324
|
-
// backup: z.boolean().default(false).describe('修改前是否创建备份,默认 false')
|
|
325
|
-
// }),
|
|
326
|
-
// execute: async (args, framework) => {
|
|
327
|
-
// const filePath = args.filePath || args.path
|
|
328
|
-
// const find = args.find
|
|
329
|
-
// const replace = args.replace
|
|
330
|
-
// const replaceAll = args.replaceAll !== false // 默认 true
|
|
331
|
-
// const useRegex = args.useRegex === true
|
|
332
|
-
// const backup = args.backup !== false // 默认 true
|
|
333
|
-
|
|
334
|
-
// // 校验 filePath
|
|
335
|
-
// if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
|
|
336
|
-
// return { success: false, error: 'filePath 是必填参数,不能为空' }
|
|
337
|
-
// }
|
|
338
|
-
|
|
339
|
-
// // 校验 find
|
|
340
|
-
// if (!find || typeof find !== 'string' || find.trim() === '') {
|
|
341
|
-
// return { success: false, error: 'find 是必填参数,不能为空字符串' }
|
|
342
|
-
// }
|
|
343
|
-
|
|
344
|
-
// // 路径安全验证
|
|
345
|
-
// const pathCheck = validatePath(filePath)
|
|
346
|
-
// if (!pathCheck.valid) {
|
|
347
|
-
// return { success: false, error: pathCheck.error }
|
|
348
|
-
// }
|
|
349
|
-
|
|
350
|
-
// try {
|
|
351
|
-
// if (!fs.existsSync(pathCheck.resolved)) {
|
|
352
|
-
// return { success: false, error: '文件不存在' }
|
|
353
|
-
// }
|
|
354
|
-
|
|
355
|
-
// // 检查是否是二进制文件
|
|
356
|
-
// const stats = fs.statSync(pathCheck.resolved)
|
|
357
|
-
// if (stats.size > 10 * 1024 * 1024) {
|
|
358
|
-
// return { success: false, error: '文件超过 10MB,不支持修改' }
|
|
359
|
-
// }
|
|
360
|
-
|
|
361
|
-
// let content = fs.readFileSync(pathCheck.resolved, 'utf8')
|
|
362
|
-
// let count = 0
|
|
363
|
-
// const matches = []
|
|
364
|
-
|
|
365
|
-
// if (useRegex) {
|
|
366
|
-
// // 正则表达式模式
|
|
367
|
-
// const flags = replaceAll ? 'g' : ''
|
|
368
|
-
// const regex = new RegExp(find, flags)
|
|
369
|
-
|
|
370
|
-
// let match
|
|
371
|
-
// while ((match = regex.exec(content)) !== null) {
|
|
372
|
-
// matches.push({
|
|
373
|
-
// index: match.index,
|
|
374
|
-
// length: match[0].length,
|
|
375
|
-
// text: match[0]
|
|
376
|
-
// })
|
|
377
|
-
// if (!replaceAll) break
|
|
378
|
-
// }
|
|
379
|
-
|
|
380
|
-
// if (matches.length > 0) {
|
|
381
|
-
// count = replaceAll ? matches.length : 1
|
|
382
|
-
// content = content.replace(regex, replace)
|
|
383
|
-
// }
|
|
384
|
-
// } else {
|
|
385
|
-
// // 精确字符串匹配
|
|
386
|
-
// const escapedFind = find.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
387
|
-
// const flags = replaceAll ? 'g' : ''
|
|
388
|
-
// const regex = new RegExp(escapedFind, flags)
|
|
389
|
-
|
|
390
|
-
// let match
|
|
391
|
-
// while ((match = regex.exec(content)) !== null) {
|
|
392
|
-
// matches.push({
|
|
393
|
-
// index: match.index,
|
|
394
|
-
// length: match[0].length,
|
|
395
|
-
// text: match[0]
|
|
396
|
-
// })
|
|
397
|
-
// if (!replaceAll) break
|
|
398
|
-
// }
|
|
399
|
-
|
|
400
|
-
// if (matches.length > 0) {
|
|
401
|
-
// count = replaceAll ? matches.length : 1
|
|
402
|
-
// content = content.replace(regex, replace)
|
|
403
|
-
// }
|
|
404
|
-
// }
|
|
405
|
-
|
|
406
|
-
// if (count === 0) {
|
|
407
|
-
// return {
|
|
408
|
-
// success: false,
|
|
409
|
-
// error: `未找到匹配文本: "${find.substring(0, 50)}${find.length > 50 ? '...' : ''}"`,
|
|
410
|
-
// filePath
|
|
411
|
-
// }
|
|
412
|
-
// }
|
|
413
|
-
|
|
414
|
-
// // 创建备份
|
|
415
|
-
// if (backup) {
|
|
416
|
-
// const backupPath = pathCheck.resolved + '.bak'
|
|
417
|
-
// fs.writeFileSync(backupPath, fs.readFileSync(pathCheck.resolved), 'utf8')
|
|
418
|
-
// }
|
|
419
|
-
|
|
420
|
-
// // 原子写入:先写临时文件,再 rename
|
|
421
|
-
// const tempPath = pathCheck.resolved + '.tmp.' + Date.now()
|
|
422
|
-
// fs.writeFileSync(tempPath, content, 'utf8')
|
|
423
|
-
|
|
424
|
-
// // 验证写入内容
|
|
425
|
-
// const verifyContent = fs.readFileSync(tempPath, 'utf8')
|
|
426
|
-
// if (verifyContent !== content) {
|
|
427
|
-
// fs.unlinkSync(tempPath)
|
|
428
|
-
// return { success: false, error: '写入验证失败,内容不匹配' }
|
|
429
|
-
// }
|
|
430
|
-
|
|
431
|
-
// fs.renameSync(tempPath, pathCheck.resolved)
|
|
432
|
-
|
|
433
|
-
// return {
|
|
434
|
-
// success: true,
|
|
435
|
-
// message: `文件已修改: ${pathCheck.resolved}`,
|
|
436
|
-
// filePath: pathCheck.resolved,
|
|
437
|
-
// replacements: count,
|
|
438
|
-
// matches: matches.slice(0, 5), // 最多返回5个匹配位置
|
|
439
|
-
// backupCreated: backup
|
|
440
|
-
// }
|
|
441
|
-
// } catch (error) {
|
|
442
|
-
// return { success: false, error: error.message }
|
|
443
|
-
// }
|
|
444
|
-
// }
|
|
445
|
-
// })
|
|
446
|
-
|
|
447
|
-
// ── 精确编辑工具(diff 感知) ────────────────────────────────
|
|
448
|
-
// 标准名 edit,LLM(尤其 Claude)训练时就使用这个名称
|
|
449
296
|
const editToolDef = {
|
|
450
297
|
name: 'edit',
|
|
451
|
-
description:
|
|
452
|
-
|
|
453
|
-
注意:modify_file 是旧版工具,请优先使用 edit。
|
|
454
|
-
|
|
455
|
-
edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldText(原文)和 newText(新文本)指定。
|
|
456
|
-
|
|
457
|
-
替换逻辑:
|
|
458
|
-
- 先尝试精确匹配 oldText,失败后使用模糊匹配(忽略引号、破折号、空白差异)
|
|
459
|
-
- 所有 edits 匹配原始文件内容,不依赖先后顺序
|
|
460
|
-
- 输出结果包含 unified diff 格式的变更摘要
|
|
461
|
-
|
|
462
|
-
适用场景:
|
|
463
|
-
1. 修改函数/类中的特定代码块
|
|
464
|
-
2. 同时修改文件中多个不重叠的部分
|
|
465
|
-
3. 重命名变量或导入语句
|
|
466
|
-
|
|
467
|
-
注意:
|
|
468
|
-
- oldText 必须唯一,不能有多个匹配
|
|
469
|
-
- edits 之间不能重叠,否则会报错`,
|
|
298
|
+
description: `精确编辑文件,支持一个或多个不重叠的文本替换。`,
|
|
470
299
|
inputSchema: z.object({
|
|
471
300
|
path: z.string().describe('要编辑的文件路径'),
|
|
472
301
|
edits: z.array(z.object({
|
|
@@ -476,23 +305,20 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
476
305
|
}),
|
|
477
306
|
execute: async (args, framework) => {
|
|
478
307
|
const { path: filePath, edits } = args;
|
|
479
|
-
const editDiff = require('
|
|
308
|
+
const editDiff = require('../../../src/common/diff');
|
|
480
309
|
const result = await editDiff.applyEditsToFile(filePath, edits, framework?.getCwd?.() ?? process.cwd());
|
|
481
310
|
return result;
|
|
482
311
|
}
|
|
483
312
|
};
|
|
484
313
|
|
|
485
|
-
// 注册为 edit(标准名称)
|
|
486
314
|
framework.registerTool(editToolDef);
|
|
487
315
|
|
|
488
|
-
// 别名 edit_file(兼容旧调用)
|
|
489
316
|
framework.registerTool({
|
|
490
317
|
...editToolDef,
|
|
491
318
|
name: 'edit_file',
|
|
492
319
|
description: 'edit 工具的别名,推荐直接使用 edit',
|
|
493
320
|
});
|
|
494
321
|
|
|
495
|
-
// 搜索文件
|
|
496
322
|
framework.registerTool({
|
|
497
323
|
name: 'search_file',
|
|
498
324
|
description: '在文件或目录中搜索文本,支持精确匹配和正则表达式',
|
|
@@ -529,14 +355,12 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
529
355
|
let regex
|
|
530
356
|
|
|
531
357
|
if (useRegex) {
|
|
532
|
-
// 直接使用正则表达式
|
|
533
358
|
try {
|
|
534
359
|
regex = new RegExp(pattern, caseSensitive ? 'g' : 'gi')
|
|
535
360
|
} catch (e) {
|
|
536
361
|
return { success: false, error: `无效的正则表达式: ${e.message}` }
|
|
537
362
|
}
|
|
538
363
|
} else {
|
|
539
|
-
// 转义特殊字符作为精确匹配
|
|
540
364
|
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
541
365
|
regex = new RegExp(escaped, caseSensitive ? 'g' : 'gi')
|
|
542
366
|
}
|
|
@@ -551,10 +375,9 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
551
375
|
for (let i = 0; i < lines.length; i++) {
|
|
552
376
|
if (fileResultsCount >= maxResultsPerFile) break
|
|
553
377
|
const line = lines[i]
|
|
554
|
-
regex.lastIndex = 0
|
|
378
|
+
regex.lastIndex = 0
|
|
555
379
|
|
|
556
380
|
if (regex.test(line)) {
|
|
557
|
-
// 找到所有匹配的位置
|
|
558
381
|
const lineMatches = []
|
|
559
382
|
regex.lastIndex = 0
|
|
560
383
|
let match
|
|
@@ -591,7 +414,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
591
414
|
return matches
|
|
592
415
|
}
|
|
593
416
|
|
|
594
|
-
// 搜索单个文件
|
|
595
417
|
if (targetFile) {
|
|
596
418
|
const fullPath = path.resolve(targetFile)
|
|
597
419
|
if (!fs.existsSync(fullPath)) {
|
|
@@ -611,7 +433,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
611
433
|
return { success: false, error: `读取文件失败: ${e.message}` }
|
|
612
434
|
}
|
|
613
435
|
} else {
|
|
614
|
-
// 搜索目录
|
|
615
436
|
const searchDir = (currentPath, depth = 0) => {
|
|
616
437
|
if (depth > 10 || results.length >= maxResults) return
|
|
617
438
|
|
|
@@ -619,7 +440,7 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
619
440
|
try {
|
|
620
441
|
entries = fs.readdirSync(currentPath, { withFileTypes: true })
|
|
621
442
|
} catch (e) {
|
|
622
|
-
return
|
|
443
|
+
return
|
|
623
444
|
}
|
|
624
445
|
|
|
625
446
|
for (const entry of entries) {
|
|
@@ -628,7 +449,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
628
449
|
const fullPath = path.join(currentPath, entry.name)
|
|
629
450
|
const relativePath = path.relative(cwd, fullPath)
|
|
630
451
|
|
|
631
|
-
// 检查是否在排除目录中
|
|
632
452
|
const shouldExclude = excludeDirs.some(exclude =>
|
|
633
453
|
relativePath.includes(exclude)
|
|
634
454
|
)
|
|
@@ -637,7 +457,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
637
457
|
if (entry.isDirectory()) {
|
|
638
458
|
searchDir(fullPath, depth + 1)
|
|
639
459
|
} else if (entry.isFile()) {
|
|
640
|
-
// 文件类型过滤
|
|
641
460
|
if (fileType && !entry.name.endsWith(fileType)) continue
|
|
642
461
|
|
|
643
462
|
const ext = path.extname(entry.name).toLowerCase()
|
|
@@ -652,7 +471,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
652
471
|
results.push(match)
|
|
653
472
|
}
|
|
654
473
|
} catch (e) {
|
|
655
|
-
// 跳过无法读取的文件
|
|
656
474
|
}
|
|
657
475
|
}
|
|
658
476
|
}
|
|
@@ -661,7 +479,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
661
479
|
searchDir(dirPath)
|
|
662
480
|
}
|
|
663
481
|
|
|
664
|
-
// 计算统计信息
|
|
665
482
|
const filesWithMatches = new Set(results.map(r => r.file)).size
|
|
666
483
|
const totalMatches = results.reduce((sum, r) => sum + (r.matches?.length || 1), 0)
|
|
667
484
|
|
|
@@ -684,7 +501,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
684
501
|
}
|
|
685
502
|
})
|
|
686
503
|
|
|
687
|
-
// 执行命令
|
|
688
504
|
framework.registerTool({
|
|
689
505
|
name: 'execute_command',
|
|
690
506
|
description: '执行终端命令',
|
|
@@ -698,22 +514,21 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
698
514
|
execute: async (args, framework) => {
|
|
699
515
|
const command = args.cmd || args.command || args.run
|
|
700
516
|
const cwd = args.cwd || (framework?.getCwd?.() ?? process.cwd())
|
|
701
|
-
const timeout = Math.min(args.timeout || 30000, 120000)
|
|
517
|
+
const timeout = Math.min(args.timeout || 30000, 120000)
|
|
702
518
|
|
|
703
|
-
// 验证命令:检查危险的 shell 模式
|
|
704
519
|
const dangerousPatterns = [
|
|
705
|
-
/;\s*rm\s+/i,
|
|
706
|
-
/\|\s*rm\s+/i,
|
|
707
|
-
/&&\s*rm\s+/i,
|
|
708
|
-
/;\s*del\s+/i,
|
|
709
|
-
/\|\s*del\s+/i,
|
|
710
|
-
/&\s*rm\s+/i,
|
|
711
|
-
/;\s*format\s+/i,
|
|
712
|
-
/&\s*format\s+/i,
|
|
713
|
-
/\$\(/i,
|
|
714
|
-
/`[^`]+`/i,
|
|
715
|
-
/>\s*\/dev\//i,
|
|
716
|
-
/<\s*\/dev\//i,
|
|
520
|
+
/;\s*rm\s+/i,
|
|
521
|
+
/\|\s*rm\s+/i,
|
|
522
|
+
/&&\s*rm\s+/i,
|
|
523
|
+
/;\s*del\s+/i,
|
|
524
|
+
/\|\s*del\s+/i,
|
|
525
|
+
/&\s*rm\s+/i,
|
|
526
|
+
/;\s*format\s+/i,
|
|
527
|
+
/&\s*format\s+/i,
|
|
528
|
+
/\$\(/i,
|
|
529
|
+
/`[^`]+`/i,
|
|
530
|
+
/>\s*\/dev\//i,
|
|
531
|
+
/<\s*\/dev\//i,
|
|
717
532
|
]
|
|
718
533
|
|
|
719
534
|
for (const pattern of dangerousPatterns) {
|
|
@@ -725,7 +540,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
725
540
|
}
|
|
726
541
|
}
|
|
727
542
|
|
|
728
|
-
// 验证 cwd 路径
|
|
729
543
|
const resolvedCwd = path.resolve(cwd)
|
|
730
544
|
if (!fs.existsSync(resolvedCwd)) {
|
|
731
545
|
return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
|
|
@@ -751,7 +565,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
751
565
|
}
|
|
752
566
|
})
|
|
753
567
|
|
|
754
|
-
// 执行 Bash 命令
|
|
755
568
|
framework.registerTool({
|
|
756
569
|
name: 'bash',
|
|
757
570
|
description: '执行 Bash 命令(支持 Linux/macOS/Windows Git Bash)',
|
|
@@ -770,7 +583,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
770
583
|
return { success: false, error: 'cmd/command 是必填参数' }
|
|
771
584
|
}
|
|
772
585
|
|
|
773
|
-
// 验证命令:检查危险的 shell 模式
|
|
774
586
|
const dangerousPatterns = [
|
|
775
587
|
/;\s*rm\s+-rf/i,
|
|
776
588
|
/\|\s*rm\s+/i,
|
|
@@ -791,18 +603,15 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
791
603
|
}
|
|
792
604
|
}
|
|
793
605
|
|
|
794
|
-
// 验证 cwd 路径
|
|
795
606
|
const resolvedCwd = path.resolve(cwd)
|
|
796
607
|
if (!fs.existsSync(resolvedCwd)) {
|
|
797
608
|
return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
|
|
798
609
|
}
|
|
799
610
|
|
|
800
|
-
// 确定 bash 路径
|
|
801
611
|
let bashPath = 'bash'
|
|
802
612
|
const isWindows = process.platform === 'win32'
|
|
803
613
|
|
|
804
614
|
if (isWindows) {
|
|
805
|
-
// Windows 下尝试查找 Git Bash
|
|
806
615
|
const possiblePaths = [
|
|
807
616
|
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
808
617
|
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
@@ -851,7 +660,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
851
660
|
}
|
|
852
661
|
})
|
|
853
662
|
|
|
854
|
-
// 获取北京时间
|
|
855
663
|
framework.registerTool({
|
|
856
664
|
name: 'get_time',
|
|
857
665
|
description: '获取当前的时间,需要获取日期和时间的时候需要调用该接口',
|
|
@@ -878,7 +686,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
878
686
|
}
|
|
879
687
|
})
|
|
880
688
|
|
|
881
|
-
// HTTP 请求
|
|
882
689
|
framework.registerTool({
|
|
883
690
|
name: 'fetch',
|
|
884
691
|
description: '发送 HTTP 请求获取远程数据。HTML 内容会保存到临时文件并返回路径;非 HTML 内容可选择转换为 Markdown。注意:如果访问失败或超时,LLM 应自动使用 proxy: true 重试。',
|
|
@@ -920,17 +727,14 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
920
727
|
data = text
|
|
921
728
|
}
|
|
922
729
|
|
|
923
|
-
// 检测是否为 HTML 内容
|
|
924
730
|
const isHtml = typeof data === 'string' && (/<[a-z][\s\S]*>/i.test(data) || data.trim().startsWith('<'))
|
|
925
731
|
|
|
926
|
-
// HTML 内容统一保存到临时文件
|
|
927
732
|
if (isHtml) {
|
|
928
733
|
const tmpDir = os.tmpdir()
|
|
929
734
|
const filename = `fetch_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.html`
|
|
930
735
|
const filePath = path.join(tmpDir, filename)
|
|
931
736
|
fs.writeFileSync(filePath, data, 'utf-8')
|
|
932
737
|
|
|
933
|
-
// toMarkdown 为 true 时返回 markdown
|
|
934
738
|
if (toMarkdown) {
|
|
935
739
|
try {
|
|
936
740
|
const markdown = NodeHtmlMarkdown.translate(data)
|
|
@@ -945,7 +749,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
945
749
|
content: markdown
|
|
946
750
|
}
|
|
947
751
|
} catch (e) {
|
|
948
|
-
// 转换失败时只返回路径
|
|
949
752
|
return {
|
|
950
753
|
success: true,
|
|
951
754
|
status: response.status,
|
|
@@ -969,7 +772,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
969
772
|
}
|
|
970
773
|
}
|
|
971
774
|
|
|
972
|
-
// 非 HTML 内容
|
|
973
775
|
if (toMarkdown && typeof data === 'string') {
|
|
974
776
|
try {
|
|
975
777
|
const markdown = NodeHtmlMarkdown.translate(data)
|
|
@@ -988,11 +790,9 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
988
790
|
truncated: maxLength && markdown.length > maxLength
|
|
989
791
|
}
|
|
990
792
|
} catch (e) {
|
|
991
|
-
// 转换失败时返回原始 body
|
|
992
793
|
}
|
|
993
794
|
}
|
|
994
795
|
|
|
995
|
-
// 返回原始内容
|
|
996
796
|
const truncatedData = maxLength && typeof data === 'string' && data.length > maxLength
|
|
997
797
|
? data.substring(0, maxLength) + '\n\n...(truncated)'
|
|
998
798
|
: data
|
|
@@ -1022,7 +822,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
1022
822
|
}
|
|
1023
823
|
})
|
|
1024
824
|
|
|
1025
|
-
// 发送通知
|
|
1026
825
|
framework.registerTool({
|
|
1027
826
|
name: 'notification_send',
|
|
1028
827
|
description: '发送系统通知,仅发送给当前聊天会话,通知会显示给用户或在下次对话时呈现',
|
|
@@ -1034,7 +833,6 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
1034
833
|
execute: async (args, framework) => {
|
|
1035
834
|
const { title, message, source = '系统消息' } = args
|
|
1036
835
|
try {
|
|
1037
|
-
// 获取当前执行上下文中的 sessionId,只发送到当前会话
|
|
1038
836
|
const ctx = framework.getExecutionContext()
|
|
1039
837
|
const sessionId = ctx?.sessionId || null
|
|
1040
838
|
|
|
@@ -1056,4 +854,4 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
|
|
|
1056
854
|
}
|
|
1057
855
|
}
|
|
1058
856
|
|
|
1059
|
-
module.exports = FileSystemPlugin
|
|
857
|
+
module.exports = FileSystemPlugin
|