foliko 1.0.7 → 1.0.9
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 +7 -1
- package/.env.example +23 -0
- package/README.md +29 -2
- package/SPEC.md +75 -2
- package/cli/src/ui/chat-ui.js +41 -2
- package/docs/quick-reference.md +30 -4
- package/docs/user-manual.md +158 -3
- package/{test-chat.js → examples/test-chat.js} +2 -2
- package/{test-mcp.js → examples/test-mcp.js} +2 -2
- package/{test-reload.js → examples/test-reload.js} +2 -2
- package/{test-telegram.js → examples/test-telegram.js} +1 -1
- package/{test-tg-bot.js → examples/test-tg-bot.js} +1 -1
- package/{test-tg.js → examples/test-tg.js} +1 -1
- package/{test-think.js → examples/test-think.js} +1 -1
- package/package.json +4 -1
- package/plugins/ai-plugin.js +8 -0
- package/plugins/default-plugins.js +139 -59
- package/plugins/email.js +382 -0
- package/plugins/install-plugin.js +115 -12
- package/plugins/telegram-plugin.js +9 -0
- package/plugins/tools-plugin.js +75 -0
- package/skills/vb-agent-dev/AGENTS.md +81 -10
- package/skills/vb-agent-dev/SKILL.md +149 -25
- package/src/core/framework.js +27 -0
- package/src/core/plugin-manager.js +272 -16
- /package/{test-tg-simple.js → examples/test-tg-simple.js} +0 -0
|
@@ -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 (
|
|
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 (
|
|
202
|
-
|
|
203
|
-
} else
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
475
|
+
// 扫描所有插件名称(支持文件夹和单文件)
|
|
476
|
+
const pluginNames = scanPluginNames(pluginsDir)
|
|
411
477
|
|
|
412
|
-
for (const
|
|
478
|
+
for (const pluginName of pluginNames) {
|
|
413
479
|
try {
|
|
414
|
-
const
|
|
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
|
|
505
|
+
const resolvedPluginName = tempPlugin.name || pluginName
|
|
434
506
|
|
|
435
507
|
// 如果插件已加载且已启动,跳过
|
|
436
|
-
if (framework.pluginManager.has(
|
|
437
|
-
framework.pluginManager.get(
|
|
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: ${
|
|
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 ${
|
|
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
|
}
|