foliko 1.0.8 → 1.0.10

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.
@@ -182,8 +182,29 @@ async function bootstrapDefaults(framework, config = {}) {
182
182
  // 合并 AI 配置
183
183
  const aiConfig = { ...agentConfig.ai, ...config.aiConfig }
184
184
 
185
+ // 核心插件列表(不能禁用,必须加载)
186
+ const CORE_PLUGINS = new Set([
187
+ 'install', 'ai', 'storage', 'tools', 'workflow', 'skill-manager',
188
+ 'mcp-executor', 'shell-executor', 'python-executor', 'session',
189
+ 'audit', 'rules', 'scheduler', 'file-system', 'think',
190
+ 'python-plugin-loader', 'telegram'
191
+ ])
192
+
193
+ // 辅助函数:检查插件是否应该加载(核心插件不能禁用)
194
+ const shouldLoad = (name) => {
195
+ if (framework.pluginManager.has(name)) {
196
+ console.log(`[Bootstrap] ${name} Plugin already loaded, skipping`)
197
+ return false
198
+ }
199
+ // 核心插件不能禁用
200
+ if (CORE_PLUGINS.has(name) && framework.pluginManager.isEnabled(name) === false) {
201
+ console.log(`[Bootstrap] ${name} is a core plugin, cannot be disabled`)
202
+ }
203
+ return true
204
+ }
205
+
185
206
  // 0. Install 工具插件(最先加载,让其他插件能用到它的 node_modules)
186
- if (!framework.pluginManager.has('install')) {
207
+ if (shouldLoad('install')) {
187
208
  const { InstallPlugin } = require('./install-plugin')
188
209
  await framework.loadPlugin(new InstallPlugin({ agentDir: agentConfig.agentDir }))
189
210
  console.log('[Bootstrap] Install Plugin loaded')
@@ -197,10 +218,10 @@ async function bootstrapDefaults(framework, config = {}) {
197
218
 
198
219
  console.log('[Bootstrap] Loading default plugins...')
199
220
 
200
- // 检查是否已经加载
201
- if (framework.pluginManager.has('ai')) {
202
- console.log('[Bootstrap] AI Plugin already loaded, skipping')
203
- } else if (aiConfig.provider || aiConfig.model || aiConfig.apiKey) {
221
+ // AI 插件(如果已禁用则跳过)
222
+ if (!shouldLoad('ai') || !(aiConfig.provider || aiConfig.model || aiConfig.apiKey)) {
223
+ // 跳过或已禁用
224
+ } else {
204
225
  const { AIPlugin } = require('./ai-plugin')
205
226
  const aiPlugin = new AIPlugin({
206
227
  provider: aiConfig.provider || 'deepseek',
@@ -232,46 +253,38 @@ async function bootstrapDefaults(framework, config = {}) {
232
253
  }
233
254
 
234
255
  // 2. Storage 存储插件
235
- if (!framework.pluginManager.has('storage')) {
256
+ if (shouldLoad('storage')) {
236
257
  const { StoragePlugin } = require('./storage-plugin')
237
258
  await framework.loadPlugin(new StoragePlugin())
238
259
  console.log('[Bootstrap] Storage Plugin loaded')
239
- } else {
240
- console.log('[Bootstrap] Storage Plugin already loaded, skipping')
241
260
  }
242
261
 
243
262
  // 3. 内置工具插件
244
- if (!framework.pluginManager.has('tools')) {
263
+ if (shouldLoad('tools')) {
245
264
  const { ToolsPlugin } = require('./tools-plugin')
246
265
  await framework.loadPlugin(new ToolsPlugin())
247
266
  console.log('[Bootstrap] Tools Plugin loaded')
248
- } else {
249
- console.log('[Bootstrap] Tools Plugin already loaded, skipping')
250
267
  }
251
268
 
252
269
  // 4. 工作流插件
253
- if (!framework.pluginManager.has('workflow')) {
270
+ if (shouldLoad('workflow')) {
254
271
  const { WorkflowPlugin } = require('../src/capabilities/workflow-engine')
255
272
  await framework.loadPlugin(new WorkflowPlugin())
256
273
  console.log('[Bootstrap] Workflow Plugin loaded')
257
- } else {
258
- console.log('[Bootstrap] Workflow Plugin already loaded, skipping')
259
274
  }
260
275
 
261
276
  // 5. Skill 管理器插件
262
- if (!framework.pluginManager.has('skill-manager')) {
277
+ if (shouldLoad('skill-manager')) {
263
278
  if (skillsDirs.length > 0) {
264
279
  const { SkillManagerPlugin } = require('../src/capabilities/skill-manager')
265
280
  // 传递所有 skills 目录
266
281
  await framework.loadPlugin(new SkillManagerPlugin({ skillsDirs }))
267
282
  console.log('[Bootstrap] Skill Manager loaded')
268
283
  }
269
- } else {
270
- console.log('[Bootstrap] Skill Manager already loaded, skipping')
271
284
  }
272
285
 
273
286
  // 6. MCP 执行器插件(始终加载,确保 mcp_reload 工具可用)
274
- if (!framework.pluginManager.has('mcp-executor')) {
287
+ if (shouldLoad('mcp-executor')) {
275
288
  const mcpServers = Object.entries(agentConfig.mcpServers || {})
276
289
  const servers = mcpServers.map(([name, cfg]) => ({
277
290
  ...cfg,
@@ -285,93 +298,73 @@ async function bootstrapDefaults(framework, config = {}) {
285
298
  const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
286
299
  await framework.loadPlugin(new MCPExecutorPlugin({ servers }))
287
300
  console.log(`[Bootstrap] MCP Executor loaded${servers.length > 0 ? ` (${servers.length} servers)` : ' (no servers)'}`)
288
- } else {
289
- console.log('[Bootstrap] MCP Executor already loaded, skipping')
290
301
  }
291
302
 
292
303
  // 7. Shell 执行器插件
293
- if (!framework.pluginManager.has('shell-executor')) {
304
+ if (shouldLoad('shell-executor')) {
294
305
  const { ShellExecutorPlugin } = require('./shell-executor-plugin')
295
306
  await framework.loadPlugin(new ShellExecutorPlugin())
296
307
  console.log('[Bootstrap] Shell Executor loaded')
297
- } else {
298
- console.log('[Bootstrap] Shell Executor already loaded, skipping')
299
308
  }
300
309
 
301
310
  // 8. Python 执行器插件
302
- if (!framework.pluginManager.has('python-executor')) {
311
+ if (shouldLoad('python-executor')) {
303
312
  const { PythonExecutorPlugin } = require('./python-executor-plugin')
304
313
  await framework.loadPlugin(new PythonExecutorPlugin())
305
314
  console.log('[Bootstrap] Python Executor loaded')
306
- } else {
307
- console.log('[Bootstrap] Python Executor already loaded, skipping')
308
315
  }
309
316
 
310
317
  // 9. Session 会话管理插件
311
- if (!framework.pluginManager.has('session')) {
318
+ if (shouldLoad('session')) {
312
319
  const { SessionPlugin } = require('./session-plugin')
313
320
  await framework.loadPlugin(new SessionPlugin())
314
321
  console.log('[Bootstrap] Session Plugin loaded')
315
- } else {
316
- console.log('[Bootstrap] Session Plugin already loaded, skipping')
317
322
  }
318
323
 
319
324
  // 10. Audit 审计日志插件
320
- if (!framework.pluginManager.has('audit')) {
325
+ if (shouldLoad('audit')) {
321
326
  const { AuditPlugin } = require('./audit-plugin')
322
327
  await framework.loadPlugin(new AuditPlugin())
323
328
  console.log('[Bootstrap] Audit Plugin loaded')
324
- } else {
325
- console.log('[Bootstrap] Audit Plugin already loaded, skipping')
326
329
  }
327
330
 
328
331
  // 10. Rules 规则引擎插件
329
- if (!framework.pluginManager.has('rules')) {
332
+ if (shouldLoad('rules')) {
330
333
  const { RulesPlugin } = require('./rules-plugin')
331
334
  await framework.loadPlugin(new RulesPlugin())
332
335
  console.log('[Bootstrap] Rules Plugin loaded')
333
- } else {
334
- console.log('[Bootstrap] Rules Plugin already loaded, skipping')
335
336
  }
336
337
 
337
338
  // 11. Scheduler 定时任务插件
338
- if (!framework.pluginManager.has('scheduler')) {
339
+ if (shouldLoad('scheduler')) {
339
340
  const { SchedulerPlugin } = require('./scheduler-plugin')
340
341
  await framework.loadPlugin(new SchedulerPlugin())
341
342
  console.log('[Bootstrap] Scheduler Plugin loaded')
342
- } else {
343
- console.log('[Bootstrap] Scheduler Plugin already loaded, skipping')
344
343
  }
345
344
 
346
345
  // 11. FileSystem 文件系统插件
347
- if (!framework.pluginManager.has('file-system')) {
346
+ if (shouldLoad('file-system')) {
348
347
  const { FileSystemPlugin } = require('./file-system-plugin')
349
348
  await framework.loadPlugin(new FileSystemPlugin())
350
349
  console.log('[Bootstrap] FileSystem Plugin loaded')
351
- } else {
352
- console.log('[Bootstrap] FileSystem Plugin already loaded, skipping')
353
350
  }
354
351
 
355
352
  // 12. Think 主动思考插件
356
- if (!framework.pluginManager.has('think')) {
353
+ if (shouldLoad('think')) {
357
354
  const { ThinkPlugin } = require('./think-plugin')
358
355
  await framework.loadPlugin(new ThinkPlugin())
359
356
  console.log('[Bootstrap] Think Plugin loaded')
360
- } else {
361
- console.log('[Bootstrap] Think Plugin already loaded, skipping')
362
357
  }
363
358
 
364
359
  // 12.5 Python 插件加载器
365
- if (!framework.pluginManager.has('python-plugin-loader')) {
360
+ if (shouldLoad('python-plugin-loader')) {
366
361
  const { PythonPluginLoader } = require('./python-plugin-loader')
367
362
  await framework.loadPlugin(new PythonPluginLoader({ agentDir: agentConfig.agentDir }))
368
363
  console.log('[Bootstrap] Python Plugin Loader loaded')
369
- } else {
370
- console.log('[Bootstrap] Python Plugin Loader already loaded, skipping')
371
364
  }
372
365
 
373
366
  // 12.6 Telegram 插件(如果配置了Token)
374
- if (!framework.pluginManager.has('telegram')) {
367
+ if (shouldLoad('telegram')) {
375
368
  const telegramToken = process.env.TELEGRAM_BOT_TOKEN || agentConfig.telegram?.botToken
376
369
  if (telegramToken) {
377
370
  const { Plugin } = require('../src/core/plugin-base')
@@ -380,8 +373,6 @@ async function bootstrapDefaults(framework, config = {}) {
380
373
  await framework.loadPlugin(new TelegramPlugin({ botToken: telegramToken }))
381
374
  console.log('[Bootstrap] Telegram Plugin loaded')
382
375
  }
383
- } else {
384
- console.log('[Bootstrap] Telegram Plugin already loaded, skipping')
385
376
  }
386
377
 
387
378
  // 13. 加载自定义插件
@@ -399,6 +390,80 @@ async function bootstrapDefaults(framework, config = {}) {
399
390
  console.log('[Bootstrap] Tools:', framework.getTools().map(t => t.name))
400
391
  }
401
392
 
393
+ /**
394
+ * 解析插件路径
395
+ * 支持两种结构:
396
+ * 1. 文件夹结构: .agent/plugins/my-plugin/index.js
397
+ * 2. 单文件结构: .agent/plugins/my-plugin.js
398
+ * @param {string} pluginsDir - 插件目录
399
+ * @param {string} name - 插件名称
400
+ * @returns {{path: string, type: 'folder'|'file'}|null} 插件路径和类型
401
+ */
402
+ function resolvePluginPath(pluginsDir, name) {
403
+ const folderPath = path.join(pluginsDir, name)
404
+ const filePath = path.join(pluginsDir, `${name}.js`)
405
+
406
+ // 文件夹优先
407
+ if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
408
+ const pkgPath = path.join(folderPath, 'package.json')
409
+ if (fs.existsSync(pkgPath)) {
410
+ try {
411
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
412
+ const main = pkg.main || 'index.js'
413
+ const mainPath = path.join(folderPath, main)
414
+ if (fs.existsSync(mainPath)) {
415
+ return { path: mainPath, type: 'folder' }
416
+ }
417
+ } catch (err) {
418
+ console.warn(`[resolvePluginPath] Failed to parse package.json for ${name}:`, err.message)
419
+ }
420
+ }
421
+ // 默认加载 index.js
422
+ const indexPath = path.join(folderPath, 'index.js')
423
+ if (fs.existsSync(indexPath)) {
424
+ return { path: indexPath, type: 'folder' }
425
+ }
426
+ console.warn(`[resolvePluginPath] No entry point found for plugin folder: ${name}`)
427
+ return null
428
+ }
429
+
430
+ // 单文件回退
431
+ if (fs.existsSync(filePath)) {
432
+ return { path: filePath, type: 'file' }
433
+ }
434
+
435
+ return null
436
+ }
437
+
438
+ /**
439
+ * 扫描插件目录,返回所有插件名称
440
+ * @param {string} pluginsDir - 插件目录
441
+ * @returns {string[]} 插件名称列表
442
+ */
443
+ function scanPluginNames(pluginsDir) {
444
+ if (!fs.existsSync(pluginsDir)) {
445
+ return []
446
+ }
447
+
448
+ const names = new Set()
449
+ const entries = fs.readdirSync(pluginsDir, { withFileTypes: true })
450
+
451
+ for (const entry of entries) {
452
+ if (entry.isDirectory()) {
453
+ // 文件夹插件
454
+ names.add(entry.name)
455
+ } else if (entry.isFile() && entry.name.endsWith('.js')) {
456
+ // 单文件插件(排除与文件夹同名的)
457
+ const baseName = entry.name.replace(/\.js$/, '')
458
+ if (!names.has(baseName)) {
459
+ names.add(baseName)
460
+ }
461
+ }
462
+ }
463
+
464
+ return Array.from(names)
465
+ }
466
+
402
467
  async function loadCustomPlugins(framework, agentConfig) {
403
468
  // 加载 .agent/plugins 目录下的自定义插件
404
469
  const pluginsDir = path.resolve(process.cwd(), '.agent', 'plugins')
@@ -407,11 +472,18 @@ async function loadCustomPlugins(framework, agentConfig) {
407
472
  return
408
473
  }
409
474
 
410
- const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.js'))
475
+ // 扫描所有插件名称(支持文件夹和单文件)
476
+ const pluginNames = scanPluginNames(pluginsDir)
411
477
 
412
- for (const file of files) {
478
+ for (const pluginName of pluginNames) {
413
479
  try {
414
- const pluginPath = path.join(pluginsDir, file)
480
+ const resolved = resolvePluginPath(pluginsDir, pluginName)
481
+ if (!resolved) {
482
+ console.warn(`[Bootstrap] Cannot resolve plugin: ${pluginName}`)
483
+ continue
484
+ }
485
+
486
+ const { path: pluginPath, type } = resolved
415
487
 
416
488
  // 清除缓存
417
489
  delete require.cache[require.resolve(pluginPath)]
@@ -430,18 +502,24 @@ async function loadCustomPlugins(framework, agentConfig) {
430
502
  // 获取插件名称
431
503
  const tempPlugin = typeof plugin === 'function' && plugin.prototype?.name
432
504
  ? new plugin() : plugin
433
- const pluginName = tempPlugin.name || file.replace('.js', '')
505
+ const resolvedPluginName = tempPlugin.name || pluginName
434
506
 
435
507
  // 如果插件已加载且已启动,跳过
436
- if (framework.pluginManager.has(pluginName) &&
437
- framework.pluginManager.get(pluginName)?._started) {
508
+ if (framework.pluginManager.has(resolvedPluginName) &&
509
+ framework.pluginManager.get(resolvedPluginName)?._started) {
510
+ continue
511
+ }
512
+
513
+ // 如果插件被禁用(在 state 文件中 enabled: false),跳过
514
+ if (framework.pluginManager.isEnabled(resolvedPluginName) === false) {
515
+ console.log(`[Bootstrap] Custom plugin '${resolvedPluginName}' is disabled, skipping`)
438
516
  continue
439
517
  }
440
518
 
441
- console.log(`[Bootstrap] Loading custom plugin: ${file}`)
519
+ console.log(`[Bootstrap] Loading custom plugin: ${pluginName} (${type})`)
442
520
  await framework.loadPlugin(plugin)
443
521
  } catch (err) {
444
- console.error(`[Bootstrap] Failed to load plugin ${file}:`, err.message)
522
+ console.error(`[Bootstrap] Failed to load plugin ${pluginName}:`, err.message)
445
523
  }
446
524
  }
447
525
  }
@@ -450,5 +528,7 @@ module.exports = {
450
528
  DefaultPlugins,
451
529
  loadAgentConfig,
452
530
  bootstrapDefaults,
453
- loadCustomPlugins
531
+ loadCustomPlugins,
532
+ resolvePluginPath,
533
+ scanPluginNames
454
534
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * InstallPlugin - npm 包安装工具
3
- * 自动安装缺少的 node 模块到 .agent 目录
3
+ * 自动安装缺少的 node 模块到 .agent 目录或指定目录
4
4
  */
5
5
 
6
6
  const { execSync } = require('child_process')
@@ -14,7 +14,7 @@ class InstallPlugin extends Plugin {
14
14
  super()
15
15
  this.name = 'install'
16
16
  this.version = '1.0.0'
17
- this.description = '自动安装 npm 包到 .agent 目录'
17
+ this.description = '自动安装 npm 包到指定目录'
18
18
 
19
19
  this._agentDir = config.agentDir || '.agent'
20
20
  this._nodeModulesDir = null
@@ -35,35 +35,73 @@ class InstallPlugin extends Plugin {
35
35
  // 注册 install 工具
36
36
  framework.registerTool({
37
37
  name: 'install',
38
- description: '安装 npm 包到 .agent 目录,用于解决插件依赖缺失问题',
38
+ description: '安装 npm 包到指定目录',
39
39
  inputSchema: z.object({
40
- package: z.string().describe('包名,如 lodashlodash@4.17.21')
40
+ package: z.string().optional().describe('包名,如 lodashlodash@4.17.21 或 @scope/package'),
41
+ path: z.string().optional().describe('安装目标目录,如 .agent/plugins/my-plugin(默认为 .agent)'),
42
+ file: z.string().optional().describe('本地 package.json 路径,从该文件安装所有依赖')
41
43
  }),
42
44
  execute: async (args) => {
43
- const { package: packageName } = args
44
- return this._installPackage(packageName)
45
+ const { package: packageName, path: targetPath, file: packageJsonPath } = args
46
+
47
+ // 优先处理 file 参数(从 package.json 安装)
48
+ if (packageJsonPath) {
49
+ const resolvedPath = path.resolve(process.cwd(), packageJsonPath)
50
+ return this._installFromPackageJson(resolvedPath, targetPath)
51
+ }
52
+
53
+ // package 和 path 都提供
54
+ if (packageName) {
55
+ return this._installPackage(packageName, targetPath)
56
+ }
57
+
58
+ // 只有 path,安装该目录的 package.json
59
+ if (targetPath) {
60
+ const resolvedPath = path.resolve(process.cwd(), targetPath)
61
+ const pkgJson = path.join(resolvedPath, 'package.json')
62
+ if (fs.existsSync(pkgJson)) {
63
+ return this._installFromPackageJson(pkgJson, targetPath)
64
+ }
65
+ return { success: false, error: `package.json not found at ${pkgJson}` }
66
+ }
67
+
68
+ // 什么都不提供,报错
69
+ return { success: false, error: 'Must provide package name or package.json path' }
45
70
  }
46
71
  })
47
72
 
48
73
  return this
49
74
  }
50
75
 
51
- _installPackage(packageName) {
76
+ /**
77
+ * 安装单个包到指定目录
78
+ * @param {string} packageName - 包名
79
+ * @param {string|null} targetPath - 目标目录,默认为 .agent
80
+ */
81
+ _installPackage(packageName, targetPath = null) {
52
82
  try {
53
- console.log(`[InstallPlugin] Installing ${packageName} to ${this._nodeModulesDir}...`)
83
+ const installPath = targetPath
84
+ ? path.resolve(process.cwd(), targetPath)
85
+ : this._agentDir
86
+
87
+ console.log(`[InstallPlugin] Installing ${packageName} to ${installPath}...`)
88
+
89
+ // 确保目标目录存在
90
+ if (!fs.existsSync(installPath)) {
91
+ fs.mkdirSync(installPath, { recursive: true })
92
+ }
54
93
 
55
94
  // 使用 npm install 安装到指定目录
56
- // --prefix 确保安装到 .agent/node_modules
57
- execSync(`npm install ${packageName} --prefix "${this._agentDir}"`, {
95
+ execSync(`npm install ${packageName} --prefix "${installPath}"`, {
58
96
  stdio: 'inherit',
59
- cwd: this._agentDir
97
+ cwd: installPath
60
98
  })
61
99
 
62
100
  console.log(`[InstallPlugin] Successfully installed ${packageName}`)
63
101
 
64
102
  return {
65
103
  success: true,
66
- message: `Successfully installed ${packageName}`
104
+ message: `Successfully installed ${packageName} to ${installPath}`
67
105
  }
68
106
  } catch (err) {
69
107
  return {
@@ -73,6 +111,61 @@ class InstallPlugin extends Plugin {
73
111
  }
74
112
  }
75
113
 
114
+ /**
115
+ * 从 package.json 安装所有依赖
116
+ * @param {string} packageJsonPath - package.json 路径
117
+ * @param {string|null} targetPath - 目标目录,默认为 package.json 所在目录
118
+ */
119
+ _installFromPackageJson(packageJsonPath, targetPath = null) {
120
+ try {
121
+ const resolvedPkgPath = path.resolve(process.cwd(), packageJsonPath)
122
+ const pkgDir = path.dirname(resolvedPkgPath)
123
+ const installPath = targetPath
124
+ ? path.resolve(process.cwd(), targetPath)
125
+ : pkgDir
126
+
127
+ console.log(`[InstallPlugin] Installing dependencies from ${resolvedPkgPath} to ${installPath}...`)
128
+
129
+ // 读取 package.json 获取要安装的包
130
+ const pkg = JSON.parse(fs.readFileSync(resolvedPkgPath, 'utf-8'))
131
+ const packages = []
132
+
133
+ if (pkg.dependencies) {
134
+ packages.push(...Object.keys(pkg.dependencies))
135
+ }
136
+ if (pkg.devDependencies) {
137
+ packages.push(...Object.keys(pkg.devDependencies))
138
+ }
139
+
140
+ if (packages.length === 0) {
141
+ return { success: true, message: 'No dependencies to install' }
142
+ }
143
+
144
+ // 确保目标目录存在
145
+ if (!fs.existsSync(installPath)) {
146
+ fs.mkdirSync(installPath, { recursive: true })
147
+ }
148
+
149
+ // 使用 npm install 安装
150
+ execSync(`npm install --prefix "${installPath}" --no-save`, {
151
+ stdio: 'inherit',
152
+ cwd: pkgDir
153
+ })
154
+
155
+ console.log(`[InstallPlugin] Successfully installed ${packages.length} packages`)
156
+
157
+ return {
158
+ success: true,
159
+ message: `Successfully installed ${packages.length} dependencies to ${installPath}`
160
+ }
161
+ } catch (err) {
162
+ return {
163
+ success: false,
164
+ error: `Failed to install from package.json: ${err.message}`
165
+ }
166
+ }
167
+ }
168
+
76
169
  /**
77
170
  * 获取 node_modules 路径,供其他插件使用
78
171
  */
@@ -88,6 +181,16 @@ class InstallPlugin extends Plugin {
88
181
  module.paths.unshift(this._nodeModulesDir)
89
182
  }
90
183
  }
184
+
185
+ /**
186
+ * 添加自定义路径到 module.paths
187
+ * @param {string} dir - 目录路径
188
+ */
189
+ addCustomModulePath(dir) {
190
+ if (dir && fs.existsSync(dir) && !module.paths.includes(dir)) {
191
+ module.paths.unshift(dir)
192
+ }
193
+ }
91
194
  }
92
195
 
93
196
  module.exports = { InstallPlugin }
@@ -8,26 +8,50 @@ This file provides guidance to AI coding agents on how to create plugins for the
8
8
 
9
9
  User plugins go in `.agent/plugins/` and are **automatically loaded** on bootstrap.
10
10
 
11
+ **Recommended: Use folder structure for complex plugins**
12
+
11
13
  ```
12
14
  项目目录/
13
15
  └── .agent/
14
16
  └── plugins/
15
- ├── my-plugin.js correct
16
- └── another-plugin.js correct
17
+ ├── my-plugin/ folder structure (recommended)
18
+ │ ├── package.json # optional, main field specifies entry
19
+ │ ├── index.js # default entry point
20
+ │ └── node_modules/ # optional, plugin-private dependencies
21
+ ├── another-plugin/ ✅ another folder plugin
22
+ │ └── index.js
23
+ └── legacy.js ✅ single-file still supported
17
24
  ```
18
25
 
26
+ **Why folder structure?**
27
+ - Supports `package.json` with `main` field for custom entry points
28
+ - Supports `node_modules` for plugin-private dependencies
29
+ - Better organization for complex plugins
30
+
19
31
  ### Built-in Plugins: `plugins/` (internal)
20
32
 
21
33
  Built-in framework plugins are in `plugins/` directory.
22
34
 
23
35
  ## Plugin Export Formats
24
36
 
25
- ###免引入写法 for `.agent/plugins/`
37
+ ### Folder structure (recommended for `.agent/plugins/`)
38
+
39
+ ```
40
+ .agent/plugins/my-plugin/
41
+ ├── package.json # optional
42
+ └── index.js # entry point
43
+ ```
26
44
 
27
- User plugins **must** use this format:
45
+ ```json
46
+ // package.json example
47
+ {
48
+ "name": "my-plugin",
49
+ "main": "index.js"
50
+ }
51
+ ```
28
52
 
29
53
  ```javascript
30
- // .agent/plugins/my-plugin.js
54
+ // .agent/plugins/my-plugin/index.js
31
55
  module.exports = function(Plugin) {
32
56
  return class MyPlugin extends Plugin {
33
57
  constructor(config = {}) {
@@ -60,10 +84,22 @@ module.exports = function(Plugin) {
60
84
  }
61
85
  ```
62
86
 
87
+ ### Single-file structure (still supported)
88
+
89
+ ```javascript
90
+ // .agent/plugins/my-plugin.js
91
+ module.exports = function(Plugin) {
92
+ return class MyPlugin extends Plugin {
93
+ // ... same as above
94
+ }
95
+ }
96
+ ```
97
+
63
98
  **Key points:**
64
99
  - `Plugin` base class passed automatically by system (no require needed)
65
100
  - Factory function returns plugin class
66
101
  - Use `require('zod')` inside methods
102
+ - Folder takes priority over single-file if both exist with same name
67
103
 
68
104
  ### Traditional format for `plugins/` (built-in)
69
105
 
@@ -149,14 +185,49 @@ The framework object provides:
149
185
  | `framework.registerTool(tool)` | Register tool |
150
186
  | `framework.getTools()` | Get all tools |
151
187
  | `framework.executeTool(name, args)` | Execute tool |
188
+ | `framework.callTool(name, args)` | Call tool (for installing deps) |
152
189
  | `framework.createAgent(config)` | Create Agent |
153
190
  | `framework.reloadPlugin(name)` | Reload single plugin |
154
191
  | `framework.reloadAllPlugins()` | Reload all plugins |
155
192
 
193
+ ## Dependency Management
194
+
195
+ Use the `install` tool to install npm packages:
196
+
197
+ | Plugin Type | Install Location | Command |
198
+ |-------------|------------------|---------|
199
+ | **Folder plugin** | `.agent/plugins/plugin-name/node_modules/` | `install({ package: "pkg", path: ".agent/plugins/plugin-name" })` |
200
+ | **Single-file plugin** | `.agent/node_modules/` | `install({ package: "pkg" })` |
201
+
202
+ ```javascript
203
+ // Folder plugin - install to plugin directory
204
+ await framework.callTool('install', {
205
+ package: 'axios',
206
+ path: '.agent/plugins/my-plugin'
207
+ })
208
+
209
+ // Single-file plugin - install to .agent
210
+ await framework.callTool('install', {
211
+ package: 'zod'
212
+ })
213
+
214
+ // Install from package.json
215
+ await framework.callTool('install', {
216
+ file: './my-plugin/package.json',
217
+ path: '.agent/plugins/my-plugin'
218
+ })
219
+ ```
220
+
221
+ **When encountering `Cannot find module 'xxx'` error:**
222
+ 1. Identify plugin directory
223
+ 2. Call `install` tool with `path` parameter for folder plugins
224
+ 3. Reload plugin
225
+ 4. Report status to user
226
+
156
227
  ## Common Mistakes
157
228
 
158
- 1. ❌ Creating directory `plugins/my-plugin/index.js` - must be single file
159
- 2. ❌ Forgetting `install()` - tools won't be registered
160
- 3. ❌ Forgetting `return this` - chain calls will fail
161
- 4. ❌ Using `parameters` instead of `inputSchema`
162
- 5. ❌ Registering tools outside `install()`
229
+ 1. ❌ Forgetting `install()` - tools won't be registered
230
+ 2. ❌ Forgetting `return this` - chain calls will fail
231
+ 3. ❌ Using `parameters` instead of `inputSchema`
232
+ 4. ❌ Registering tools outside `install()`
233
+ 5. ❌ Using same name for folder and file - folder takes priority, file will be ignored