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.
Files changed (212) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CLAUDE.md +56 -30
  3. package/REFACTORING_PLAN.md +645 -0
  4. package/docs/architecture.md +131 -0
  5. package/docs/migration.md +57 -0
  6. package/docs/public-api.md +138 -0
  7. package/docs/usage.md +385 -0
  8. package/examples/ambient-example.js +20 -137
  9. package/examples/basic.js +21 -48
  10. package/examples/bootstrap.js +16 -74
  11. package/examples/mcp-example.js +6 -29
  12. package/examples/skill-example.js +6 -19
  13. package/examples/workflow.js +8 -56
  14. package/package.json +8 -4
  15. package/plugins/README.md +49 -0
  16. package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
  17. package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
  18. package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
  19. package/plugins/ambient/README.md +14 -0
  20. package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
  21. package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
  22. package/plugins/{ambient-agent → ambient}/index.js +2 -2
  23. package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
  24. package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
  25. package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
  26. package/plugins/core/default/bootstrap.js +224 -0
  27. package/plugins/core/default/config.js +222 -0
  28. package/plugins/core/default/index.js +58 -0
  29. package/plugins/core/mcp/index.js +1 -0
  30. package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
  31. package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
  32. package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
  33. package/plugins/{session-plugin.js → core/session/index.js} +9 -73
  34. package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
  35. package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
  36. package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
  37. package/plugins/{think-plugin.js → core/think/index.js} +24 -91
  38. package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
  39. package/plugins/default-plugins.js +6 -720
  40. package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
  41. package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
  42. package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
  43. package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
  44. package/plugins/install/README.md +9 -0
  45. package/plugins/{install-plugin.js → install/index.js} +3 -3
  46. package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
  47. package/plugins/{web-plugin.js → io/web/index.js} +11 -113
  48. package/plugins/memory/README.md +13 -0
  49. package/plugins/{memory-plugin.js → memory/index.js} +4 -18
  50. package/plugins/messaging/email/README.md +19 -0
  51. package/plugins/{email → messaging/email}/index.js +3 -3
  52. package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +4 -4
  53. package/plugins/{qq-plugin.js → messaging/qq/index.js} +6 -17
  54. package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +4 -4
  55. package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +16 -16
  56. package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
  57. package/plugins/{tools-plugin.js → tools/index.js} +68 -116
  58. package/plugins/trading/README.md +15 -0
  59. package/plugins/{gate-trading.js → trading/index.js} +8 -8
  60. package/{examples → sandbox}/test-concurrent-chat.js +2 -2
  61. package/{examples → sandbox}/test-long-chat.js +2 -2
  62. package/{examples → sandbox}/test-session-chat.js +2 -2
  63. package/{examples → sandbox}/test-web-plugin.js +1 -1
  64. package/{examples → sandbox}/test-weixin-feishu.js +2 -2
  65. package/src/agent/base.js +56 -0
  66. package/src/{core/agent-chat.js → agent/chat.js} +11 -11
  67. package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
  68. package/src/agent/index.js +111 -0
  69. package/src/agent/main.js +337 -0
  70. package/src/agent/prompt.js +78 -0
  71. package/src/agent/sub.js +198 -0
  72. package/src/agent/worker.js +104 -0
  73. package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
  74. package/{cli/src → src/cli}/commands/chat.js +25 -21
  75. package/{cli/src → src/cli}/index.js +1 -0
  76. package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
  77. package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
  78. package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
  79. package/src/common/errors.js +402 -0
  80. package/src/{utils → common}/logger.js +33 -0
  81. package/src/{utils/chat-queue.js → common/queue.js} +2 -2
  82. package/src/config/plugin-config.js +50 -0
  83. package/src/context/agent.js +32 -0
  84. package/src/context/compaction-prompts.js +170 -0
  85. package/src/context/compaction-utils.js +191 -0
  86. package/src/context/compressor.js +413 -0
  87. package/src/context/index.js +9 -0
  88. package/src/{core/context-manager.js → context/manager.js} +1 -1
  89. package/src/context/request.js +50 -0
  90. package/src/context/session.js +33 -0
  91. package/src/context/storage.js +30 -0
  92. package/src/executors/mcp-client.js +153 -0
  93. package/src/executors/mcp-desc.js +236 -0
  94. package/src/executors/mcp-executor.js +91 -956
  95. package/src/{core → framework}/command-registry.js +1 -1
  96. package/src/framework/framework.js +300 -0
  97. package/src/framework/index.js +18 -0
  98. package/src/framework/lifecycle.js +203 -0
  99. package/src/framework/loader.js +78 -0
  100. package/src/framework/registry.js +86 -0
  101. package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
  102. package/src/index.js +130 -15
  103. package/src/llm/index.js +26 -0
  104. package/src/llm/provider.js +212 -0
  105. package/src/llm/registry.js +11 -0
  106. package/src/{core/token-counter.js → llm/tokens.js} +4 -37
  107. package/src/{core/plugin-base.js → plugin/base.js} +10 -136
  108. package/src/plugin/index.js +14 -0
  109. package/src/plugin/loader.js +101 -0
  110. package/src/plugin/manager.js +484 -0
  111. package/src/{core → session}/branch-summary-auto.js +2 -2
  112. package/src/{core/chat-session.js → session/chat.js} +2 -2
  113. package/src/session/index.js +7 -0
  114. package/src/{core/session-manager.js → session/session.js} +2 -2
  115. package/src/session/ttl.js +92 -0
  116. package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
  117. package/src/tool/executor.js +85 -0
  118. package/src/tool/index.js +15 -0
  119. package/src/tool/registry.js +143 -0
  120. package/src/{core/tool-router.js → tool/router.js} +17 -124
  121. package/src/tool/schema.js +108 -0
  122. package/src/utils/data-splitter.js +1 -1
  123. package/src/utils/download.js +1 -1
  124. package/src/utils/index.js +6 -6
  125. package/src/utils/message-validator.js +1 -1
  126. package/tests/core/context-storage.test.js +46 -0
  127. package/tests/core/llm.test.js +54 -0
  128. package/tests/core/plugin.test.js +42 -0
  129. package/tests/core/tool.test.js +60 -0
  130. package/tests/setup.js +10 -0
  131. package/tests/smoke.test.js +58 -0
  132. package/vitest.config.js +9 -0
  133. package/cli/src/daemon.js +0 -149
  134. package/docs/CONTEXT_DESIGN.md +0 -1596
  135. package/docs/ai-sdk-optimization.md +0 -655
  136. package/docs/features.md +0 -120
  137. package/docs/qq-bot.md +0 -976
  138. package/docs/quick-reference.md +0 -160
  139. package/docs/user-manual.md +0 -1391
  140. package/images/geometric_shapes.jpg +0 -0
  141. package/images/sunset_mountain_lake.jpg +0 -0
  142. package/skills/poster-guide/SKILL.md +0 -792
  143. package/src/capabilities/index.js +0 -11
  144. package/src/core/agent.js +0 -808
  145. package/src/core/context-compressor.js +0 -959
  146. package/src/core/enhanced-context-compressor.js +0 -210
  147. package/src/core/framework.js +0 -1422
  148. package/src/core/index.js +0 -30
  149. package/src/core/plugin-manager.js +0 -961
  150. package/src/core/provider-registry.js +0 -159
  151. package/src/core/provider.js +0 -156
  152. package/src/core/request-context.js +0 -98
  153. package/src/core/subagent.js +0 -442
  154. package/src/core/system-prompt-builder.js +0 -120
  155. package/src/core/tool-executor.js +0 -202
  156. package/src/core/tool-registry.js +0 -517
  157. package/src/core/worker-agent.js +0 -192
  158. package/src/executors/executor-base.js +0 -58
  159. package/src/utils/error-boundary.js +0 -363
  160. package/src/utils/error.js +0 -374
  161. package/system.md +0 -1645
  162. package/website_v2/README.md +0 -57
  163. package/website_v2/SPEC.md +0 -1
  164. package/website_v2/docs/api.html +0 -128
  165. package/website_v2/docs/configuration.html +0 -147
  166. package/website_v2/docs/plugin-development.html +0 -129
  167. package/website_v2/docs/project-structure.html +0 -89
  168. package/website_v2/docs/skill-development.html +0 -85
  169. package/website_v2/index.html +0 -489
  170. package/website_v2/scripts/main.js +0 -93
  171. package/website_v2/styles/animations.css +0 -8
  172. package/website_v2/styles/docs.css +0 -83
  173. package/website_v2/styles/main.css +0 -417
  174. package/xhs_auth.json +0 -268
  175. package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
  176. /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
  177. /package/plugins/{email → messaging/email}/constants.js +0 -0
  178. /package/plugins/{email → messaging/email}/handlers.js +0 -0
  179. /package/plugins/{email → messaging/email}/monitor.js +0 -0
  180. /package/plugins/{email → messaging/email}/parser.js +0 -0
  181. /package/plugins/{email → messaging/email}/reply.js +0 -0
  182. /package/plugins/{email → messaging/email}/utils.js +0 -0
  183. /package/{examples → sandbox}/test-chat.js +0 -0
  184. /package/{examples → sandbox}/test-mcp.js +0 -0
  185. /package/{examples → sandbox}/test-reload.js +0 -0
  186. /package/{examples → sandbox}/test-telegram.js +0 -0
  187. /package/{examples → sandbox}/test-tg-bot.js +0 -0
  188. /package/{examples → sandbox}/test-tg-simple.js +0 -0
  189. /package/{examples → sandbox}/test-tg.js +0 -0
  190. /package/{examples → sandbox}/test-think.js +0 -0
  191. /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
  192. /package/{cli/src → src/cli}/commands/daemon.js +0 -0
  193. /package/{cli/src → src/cli}/commands/list.js +0 -0
  194. /package/{cli/src → src/cli}/commands/plugin.js +0 -0
  195. /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
  196. /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
  197. /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
  198. /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
  199. /package/{cli/src → src/cli}/utils/ansi.js +0 -0
  200. /package/{cli/src → src/cli}/utils/config.js +0 -0
  201. /package/{cli/src → src/cli}/utils/markdown.js +0 -0
  202. /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
  203. /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
  204. /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
  205. /package/src/{core → common}/constants.js +0 -0
  206. /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
  207. /package/src/{utils/event-emitter.js → common/events.js} +0 -0
  208. /package/src/{utils → common}/id.js +0 -0
  209. /package/src/{utils → common}/retry.js +0 -0
  210. /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
  211. /package/src/{core/session-entry.js → session/entry.js} +0 -0
  212. /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('../src/core/plugin-base')
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 // 256KB 缓冲区
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('../src/utils/edit-diff');
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 // 重置 regex 状态
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) // 最多 2 分钟
517
+ const timeout = Math.min(args.timeout || 30000, 120000)
702
518
 
703
- // 验证命令:检查危险的 shell 模式
704
519
  const dangerousPatterns = [
705
- /;\s*rm\s+/i, // ; rm -rf
706
- /\|\s*rm\s+/i, // | rm -rf
707
- /&&\s*rm\s+/i, // && rm -rf
708
- /;\s*del\s+/i, // ; del
709
- /\|\s*del\s+/i, // | del
710
- /&\s*rm\s+/i, // & rm -rf
711
- /;\s*format\s+/i, // ; format
712
- /&\s*format\s+/i, // & format
713
- /\$\(/i, // $(command substitution)
714
- /`[^`]+`/i, // `command substitution`
715
- />\s*\/dev\//i, // redirect to /dev/
716
- /<\s*\/dev\//i, // read from /dev/
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