foliko 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/22.txt +10 -0
  3. package/README.md +218 -0
  4. package/SPEC.md +452 -0
  5. package/cli/bin/foliko.js +12 -0
  6. package/cli/src/commands/chat.js +75 -0
  7. package/cli/src/index.js +64 -0
  8. package/cli/src/ui/chat-ui.js +272 -0
  9. package/cli/src/utils/ansi.js +40 -0
  10. package/cli/src/utils/markdown.js +296 -0
  11. package/docs/quick-reference.md +131 -0
  12. package/docs/user-manual.md +1205 -0
  13. package/examples/basic.js +110 -0
  14. package/examples/bootstrap.js +93 -0
  15. package/examples/mcp-example.js +53 -0
  16. package/examples/skill-example.js +49 -0
  17. package/examples/workflow.js +158 -0
  18. package/package.json +36 -0
  19. package/plugins/ai-plugin.js +89 -0
  20. package/plugins/audit-plugin.js +187 -0
  21. package/plugins/default-plugins.js +412 -0
  22. package/plugins/file-system-plugin.js +344 -0
  23. package/plugins/install-plugin.js +93 -0
  24. package/plugins/python-executor-plugin.js +331 -0
  25. package/plugins/rules-plugin.js +292 -0
  26. package/plugins/scheduler-plugin.js +426 -0
  27. package/plugins/session-plugin.js +343 -0
  28. package/plugins/shell-executor-plugin.js +196 -0
  29. package/plugins/storage-plugin.js +237 -0
  30. package/plugins/subagent-plugin.js +395 -0
  31. package/plugins/think-plugin.js +329 -0
  32. package/plugins/tools-plugin.js +114 -0
  33. package/skills/mcp-usage/SKILL.md +198 -0
  34. package/skills/vb-agent-dev/AGENTS.md +162 -0
  35. package/skills/vb-agent-dev/SKILL.md +370 -0
  36. package/src/capabilities/index.js +11 -0
  37. package/src/capabilities/skill-manager.js +319 -0
  38. package/src/capabilities/workflow-engine.js +401 -0
  39. package/src/core/agent-chat.js +311 -0
  40. package/src/core/agent.js +573 -0
  41. package/src/core/framework.js +255 -0
  42. package/src/core/index.js +19 -0
  43. package/src/core/plugin-base.js +205 -0
  44. package/src/core/plugin-manager.js +392 -0
  45. package/src/core/provider.js +108 -0
  46. package/src/core/tool-registry.js +134 -0
  47. package/src/core/tool-router.js +216 -0
  48. package/src/executors/executor-base.js +58 -0
  49. package/src/executors/mcp-executor.js +728 -0
  50. package/src/index.js +37 -0
  51. package/src/utils/event-emitter.js +97 -0
  52. package/test-chat.js +129 -0
  53. package/test-mcp.js +79 -0
  54. package/test-reload.js +61 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * PluginManager 插件管理器
3
+ * 负责插件的加载、卸载、重载
4
+ */
5
+
6
+ const { Plugin } = require('./plugin-base')
7
+
8
+ class PluginManager {
9
+ /**
10
+ * @param {Framework} framework - 框架实例
11
+ */
12
+ constructor(framework) {
13
+ this.framework = framework
14
+ this._plugins = new Map()
15
+ this._loading = false
16
+ }
17
+
18
+ /**
19
+ * 注册插件(不加载)
20
+ * @param {Plugin|Object} plugin - 插件实例或定义
21
+ */
22
+ register(plugin) {
23
+ if (!plugin.name) {
24
+ throw new Error('Plugin must have a name')
25
+ }
26
+
27
+ let pluginInstance = plugin
28
+ if (!(plugin instanceof Plugin)) {
29
+ // 从对象创建插件实例
30
+ pluginInstance = this._createFromObject(plugin)
31
+ }
32
+
33
+ this._plugins.set(pluginInstance.name, {
34
+ instance: pluginInstance,
35
+ status: 'registered'
36
+ })
37
+
38
+ this.framework.emit('plugin:registered', pluginInstance)
39
+ return this
40
+ }
41
+
42
+ /**
43
+ * 加载插件
44
+ * @param {Plugin|Object} plugin - 插件实例或定义
45
+ */
46
+ async load(plugin) {
47
+ if (this._loading) {
48
+ throw new Error('Cannot load plugin during another load operation')
49
+ }
50
+
51
+ this._loading = true
52
+ try {
53
+ let pluginInstance = plugin
54
+
55
+ if (!(plugin instanceof Plugin)) {
56
+ pluginInstance = this._createFromObject(plugin)
57
+ }
58
+
59
+ // 如果已注册,使用已注册的实例
60
+ const existing = this._plugins.get(pluginInstance.name)
61
+ if (existing) {
62
+ pluginInstance = existing.instance
63
+
64
+ // 如果已加载且已启动,直接返回
65
+ if (existing.status === 'loaded' && pluginInstance._started) {
66
+ console.warn(`[PluginManager] Plugin '${pluginInstance.name}' already loaded`)
67
+ return pluginInstance
68
+ }
69
+
70
+ // 如果已加载但未启动(重载场景),直接启动
71
+ if (existing.status === 'loaded' && !pluginInstance._started) {
72
+ try {
73
+ if (typeof pluginInstance.start === 'function') {
74
+ await pluginInstance.start(this.framework)
75
+ pluginInstance._started = true
76
+ }
77
+ } catch (err) {
78
+ console.error(`[PluginManager] Start failed for '${pluginInstance.name}':`, err.message)
79
+ }
80
+ this.framework.emit('plugin:loaded', pluginInstance)
81
+ return pluginInstance
82
+ }
83
+ } else {
84
+ // 未注册,先注册
85
+ this.register(pluginInstance)
86
+ }
87
+
88
+ const entry = this._plugins.get(pluginInstance.name)
89
+
90
+ // 调用 install
91
+ try {
92
+ await entry.instance.install(this.framework)
93
+ } catch (err) {
94
+ console.error(`[PluginManager] Install failed for '${pluginInstance.name}':`, err.message)
95
+ throw err
96
+ }
97
+
98
+ entry.status = 'loaded'
99
+
100
+ // 如果处于 bootstrap 模式,由外部统一调用 startAll()
101
+ // 否则直接启动
102
+ if (this._bootstrapping) {
103
+ // bootstrap 模式下不启动,由 bootstrapDefaults 统一启动
104
+ } else {
105
+ // 非 bootstrap 模式下直接启动
106
+ try {
107
+ if (typeof pluginInstance.start === 'function') {
108
+ await pluginInstance.start(this.framework)
109
+ pluginInstance._started = true
110
+ }
111
+ } catch (err) {
112
+ console.error(`[PluginManager] Start failed for '${pluginInstance.name}':`, err.message)
113
+ }
114
+ }
115
+
116
+ this.framework.emit('plugin:loaded', pluginInstance)
117
+ return pluginInstance
118
+ } finally {
119
+ this._loading = false
120
+ }
121
+ }
122
+
123
+ /**
124
+ * 设置 bootstrap 模式
125
+ */
126
+ setBootstrapping(value) {
127
+ this._bootstrapping = value
128
+ }
129
+
130
+ /**
131
+ * 卸载插件
132
+ * @param {string} name - 插件名称
133
+ */
134
+ async unload(name) {
135
+ const entry = this._plugins.get(name)
136
+ if (!entry) {
137
+ return false
138
+ }
139
+
140
+ const { instance } = entry
141
+
142
+ // 调用 uninstall
143
+ try {
144
+ await instance.uninstall(this.framework)
145
+ } catch (err) {
146
+ console.error(`[PluginManager] Uninstall error for '${name}':`, err.message)
147
+ }
148
+
149
+ entry.status = 'unloaded'
150
+ this.framework.emit('plugin:unloaded', instance)
151
+ return true
152
+ }
153
+
154
+ /**
155
+ * 重载插件
156
+ * @param {string} name - 插件名称
157
+ */
158
+ async reload(name) {
159
+ const entry = this._plugins.get(name)
160
+ if (!entry) {
161
+ throw new Error(`Plugin '${name}' not found`)
162
+ }
163
+
164
+ const { instance } = entry
165
+
166
+ // 调用 reload
167
+ try {
168
+ await instance.reload(this.framework)
169
+ this.framework.emit('plugin:reloaded', instance)
170
+ } catch (err) {
171
+ console.error(`[PluginManager] Reload error for '${name}':`, err.message)
172
+ throw err
173
+ }
174
+
175
+ return instance
176
+ }
177
+
178
+ /**
179
+ * 重载所有插件
180
+ */
181
+ async reloadAll() {
182
+ // 1. 重置所有插件的启动标志
183
+ for (const entry of this._plugins.values()) {
184
+ entry.instance._started = false
185
+ }
186
+
187
+ // 2. 扫描 .agent/plugins 目录,加载新插件
188
+ await this._discoverCustomPlugins()
189
+
190
+ // 3. 启动所有未启动的插件
191
+ await this.startAll()
192
+ }
193
+
194
+ /**
195
+ * 发现并加载自定义插件
196
+ * @private
197
+ */
198
+ async _discoverCustomPlugins() {
199
+ const fs = require('fs')
200
+ const path = require('path')
201
+
202
+ const pluginsDir = path.resolve(process.cwd(), '.agent', 'plugins')
203
+ if (!fs.existsSync(pluginsDir)) {
204
+ return
205
+ }
206
+
207
+ const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.js'))
208
+
209
+ // 从 pluginsDir 推导 agentDir(pluginsDir = <agentDir>/plugins)
210
+ const agentDir = path.dirname(pluginsDir)
211
+ const agentNodeModules = path.join(agentDir, 'node_modules')
212
+
213
+ for (const file of files) {
214
+ try {
215
+ const pluginPath = path.join(pluginsDir, file)
216
+
217
+ // 添加模块路径到搜索路径(优先级从高到低)
218
+ const modulePathsToAdd = [
219
+ agentNodeModules, // .agent/node_modules(项目本地安装的包)
220
+ path.join(__dirname, '..', '..', 'node_modules') // 全局安装的包
221
+ ]
222
+ for (const mp of modulePathsToAdd) {
223
+ if (fs.existsSync(mp) && !module.paths.includes(mp)) {
224
+ module.paths.unshift(mp)
225
+ }
226
+ }
227
+
228
+ // 清除缓存
229
+ delete require.cache[require.resolve(pluginPath)]
230
+ const pluginModule = require(pluginPath)
231
+
232
+ let plugin
233
+ if (typeof pluginModule === 'function') {
234
+ plugin = pluginModule
235
+ } else if (pluginModule.default) {
236
+ plugin = pluginModule.default
237
+ } else {
238
+ plugin = pluginModule
239
+ }
240
+
241
+ // 获取插件名称
242
+ let pluginName
243
+ try {
244
+ const tempPlugin = plugin.prototype instanceof require('./plugin-base')
245
+ ? new plugin() : (typeof plugin === 'function' ? plugin() : plugin)
246
+ pluginName = tempPlugin.name || file.replace('.js', '')
247
+ } catch {
248
+ pluginName = file.replace('.js', '')
249
+ }
250
+
251
+ // 如果插件已加载且已启动,跳过
252
+ if (this.has(pluginName) && this.get(pluginName)?._started) {
253
+ continue
254
+ }
255
+
256
+ console.log(`[PluginManager] Loading new plugin: ${file}`)
257
+ await this.load(plugin)
258
+ } catch (err) {
259
+ console.error(`[PluginManager] Failed to load plugin ${file}:`, err.message)
260
+ }
261
+ }
262
+ }
263
+
264
+ /**
265
+ * 获取插件
266
+ * @param {string} name - 插件名称
267
+ */
268
+ get(name) {
269
+ return this._plugins.get(name)?.instance
270
+ }
271
+
272
+ /**
273
+ * 获取所有已加载插件
274
+ */
275
+ getAll() {
276
+ return Array.from(this._plugins.values())
277
+ .filter(e => e.status === 'loaded')
278
+ .map(e => ({ name: e.instance.name, instance: e.instance }))
279
+ }
280
+
281
+ /**
282
+ * 检查插件是否存在
283
+ * @param {string} name - 插件名称
284
+ */
285
+ has(name) {
286
+ return this._plugins.has(name)
287
+ }
288
+
289
+ /**
290
+ * 检查插件是否已加载
291
+ * @param {string} name - 插件名称
292
+ */
293
+ isLoaded(name) {
294
+ return this._plugins.get(name)?.status === 'loaded'
295
+ }
296
+
297
+ /**
298
+ * 启动所有已加载但未启动的插件(按优先级排序)
299
+ */
300
+ async startAll() {
301
+ const entries = Array.from(this._plugins.values())
302
+ .filter(e => e.status === 'loaded')
303
+ .sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100))
304
+
305
+ for (const entry of entries) {
306
+ const instance = entry.instance
307
+ // 跳过已经启动过的插件
308
+ if (instance._started) {
309
+ continue
310
+ }
311
+ try {
312
+ if (typeof instance.start === 'function') {
313
+ await instance.start(this.framework)
314
+ instance._started = true
315
+ }
316
+ } catch (err) {
317
+ console.error(`[PluginManager] Start failed for '${instance.name}':`, err.message)
318
+ }
319
+ }
320
+ }
321
+
322
+ /**
323
+ * 启动所有已加载插件
324
+ * @private
325
+ */
326
+ async _startAll() {
327
+ const loaded = this.getAll()
328
+ .sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100))
329
+
330
+ for (const { instance } of loaded) {
331
+ try {
332
+ if (typeof instance.start === 'function') {
333
+ await instance.start(this.framework)
334
+ }
335
+ } catch (err) {
336
+ console.error(`[PluginManager] Start failed for '${instance.name}':`, err.message)
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * 从对象创建插件实例
343
+ * @private
344
+ */
345
+ _createFromObject(obj) {
346
+ const { Plugin } = require('./plugin-base')
347
+
348
+ // 如果是类(构造函数),直接实例化
349
+ if (typeof obj === 'function') {
350
+ // 检查是否是 Plugin 的子类(通过 prototype chain)
351
+ if (obj.prototype instanceof Plugin) {
352
+ return new obj()
353
+ }
354
+ // 否则是工厂函数,调用它获取类或实例
355
+ const result = obj(Plugin)
356
+ // 递归处理返回值
357
+ if (typeof result === 'function' && result.prototype instanceof Plugin) {
358
+ return new result()
359
+ }
360
+ return result
361
+ }
362
+
363
+ // 支持对象形式: { name, version, install, start, ... }
364
+ class AnonymousPlugin extends Plugin {
365
+ constructor() {
366
+ super()
367
+ this.name = obj.name
368
+ this.version = obj.version || '1.0.0'
369
+ this.description = obj.description || ''
370
+ this.priority = obj.priority || 100
371
+
372
+ // 如果提供了 install/start/reload/uninstall 方法,绑定它们
373
+ if (typeof obj.install === 'function') {
374
+ this.install = obj.install.bind(this)
375
+ }
376
+ if (typeof obj.start === 'function') {
377
+ this.start = obj.start.bind(this)
378
+ }
379
+ if (typeof obj.reload === 'function') {
380
+ this.reload = obj.reload.bind(this)
381
+ }
382
+ if (typeof obj.uninstall === 'function') {
383
+ this.uninstall = obj.uninstall.bind(this)
384
+ }
385
+ }
386
+ }
387
+
388
+ return new AnonymousPlugin()
389
+ }
390
+ }
391
+
392
+ module.exports = { PluginManager }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * AI Provider 工厂
3
+ * 支持多种 AI 提供商
4
+ */
5
+
6
+ const { createOpenAICompatible } = require('@ai-sdk/openai-compatible')
7
+ const { createOpenAI } = require('@ai-sdk/openai')
8
+ const { createAnthropic } = require('@ai-sdk/anthropic')
9
+
10
+ /**
11
+ * 默认提供商配置
12
+ */
13
+ const DEFAULT_PROVIDERS = {
14
+ openai: {
15
+ name: 'OpenAI',
16
+ baseURL: 'https://api.openai.com/v1'
17
+ },
18
+ ollama: {
19
+ name: 'Ollama',
20
+ baseURL: 'http://localhost:11434/v1'
21
+ },
22
+ lmstudio: {
23
+ name: 'LM Studio',
24
+ baseURL: 'http://localhost:1234/v1'
25
+ },
26
+ deepseek: {
27
+ name: 'DeepSeek',
28
+ baseURL: 'https://api.deepseek.com/v1'
29
+ },
30
+ anthropic: {
31
+ name: 'Anthropic',
32
+ baseURL: 'https://api.anthropic.com/v1'
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 创建 AI 客户端
38
+ * @param {Object} config - 配置
39
+ * @param {string} config.provider - 提供商名称
40
+ * @param {string} config.model - 模型名称
41
+ * @param {string} config.apiKey - API 密钥
42
+ * @param {string} [config.baseURL] - 自定义 API 地址
43
+ */
44
+ function createAI(config) {
45
+ const { provider, model, apiKey, baseURL } = config
46
+ const providerName = (provider || 'deepseek').toLowerCase()
47
+
48
+ // 检查是否是预定义提供商
49
+ if (DEFAULT_PROVIDERS[providerName]) {
50
+ const providerConfig = DEFAULT_PROVIDERS[providerName]
51
+ return createOpenAICompatible({
52
+ name: providerConfig.name,
53
+ baseURL: baseURL || providerConfig.baseURL,
54
+ apiKey: apiKey || 'dummy-key',
55
+ headers: provider === 'anthropic' ? {
56
+ 'x-api-key': apiKey,
57
+ 'anthropic-version': '2023-06-01'
58
+ } : undefined,
59
+ models: {
60
+ default: {
61
+ id: model || 'deepseek-chat',
62
+ streamOptions: {
63
+ includeUsage: true
64
+ }
65
+ }
66
+ }
67
+ })
68
+ }
69
+
70
+ // 自定义提供商
71
+ return createOpenAICompatible({
72
+ name: providerName || 'Custom',
73
+ baseURL: baseURL,
74
+ apiKey: apiKey || 'dummy-key',
75
+ models: {
76
+ default: {
77
+ id: model || 'gpt-4'
78
+ }
79
+ }
80
+ })
81
+ }
82
+
83
+ /**
84
+ * 获取可用提供商列表
85
+ */
86
+ function getAvailableProviders() {
87
+ return Object.entries(DEFAULT_PROVIDERS).map(([key, value]) => ({
88
+ id: key,
89
+ name: value.name,
90
+ baseURL: value.baseURL
91
+ }))
92
+ }
93
+
94
+ /**
95
+ * 创建模型实例
96
+ * @param {string} model - 模型名称
97
+ * @param {Object} provider - Provider 实例
98
+ */
99
+ function createModel(model, provider) {
100
+ return provider(model)
101
+ }
102
+
103
+ module.exports = {
104
+ createAI,
105
+ createModel,
106
+ getAvailableProviders,
107
+ DEFAULT_PROVIDERS
108
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * ToolRegistry 工具注册表
3
+ * 统一管理所有工具
4
+ */
5
+
6
+ const { EventEmitter } = require('../utils/event-emitter')
7
+
8
+ class ToolRegistry extends EventEmitter {
9
+ constructor() {
10
+ super()
11
+ this._tools = new Map()
12
+ }
13
+
14
+ /**
15
+ * 注册工具
16
+ * @param {Object} tool - 工具定义
17
+ * @param {string} tool.name - 工具名称
18
+ * @param {string} tool.description - 工具描述
19
+ * @param {Function} tool.execute - 执行函数
20
+ * @param {Object} [tool.parameters] - 参数 schema
21
+ */
22
+ register(tool) {
23
+ if (!tool.name) {
24
+ throw new Error('Tool must have a name')
25
+ }
26
+ if (typeof tool.execute !== 'function') {
27
+ throw new Error(`Tool '${tool.name}' must have an execute function`)
28
+ }
29
+
30
+ this._tools.set(tool.name, {
31
+ name: tool.name,
32
+ description: tool.description || '',
33
+ inputSchema: tool.inputSchema || null,
34
+ parameters: tool.parameters || null,
35
+ execute: tool.execute
36
+ })
37
+
38
+ this.emit('tool:registered', tool)
39
+ return this
40
+ }
41
+
42
+ /**
43
+ * 批量注册工具
44
+ * @param {Array<Object>} tools - 工具数组
45
+ */
46
+ registerMany(tools) {
47
+ for (const tool of tools) {
48
+ this.register(tool)
49
+ }
50
+ return this
51
+ }
52
+
53
+ /**
54
+ * 注销工具
55
+ * @param {string} name - 工具名称
56
+ */
57
+ unregister(name) {
58
+ const tool = this._tools.get(name)
59
+ if (tool) {
60
+ this._tools.delete(name)
61
+ this.emit('tool:unregistered', tool)
62
+ }
63
+ return this
64
+ }
65
+
66
+ /**
67
+ * 获取工具
68
+ * @param {string} name - 工具名称
69
+ * @returns {Object|undefined}
70
+ */
71
+ get(name) {
72
+ return this._tools.get(name)
73
+ }
74
+
75
+ /**
76
+ * 获取所有工具
77
+ * @returns {Array<Object>}
78
+ */
79
+ getAll() {
80
+ return Array.from(this._tools.values())
81
+ }
82
+
83
+ /**
84
+ * 检查工具是否存在
85
+ * @param {string} name - 工具名称
86
+ * @returns {boolean}
87
+ */
88
+ has(name) {
89
+ return this._tools.has(name)
90
+ }
91
+
92
+ /**
93
+ * 执行工具
94
+ * @param {string} name - 工具名称
95
+ * @param {Object} args - 参数
96
+ * @param {Framework} framework - 框架实例
97
+ * @returns {Promise<any>}
98
+ */
99
+ async execute(name, args, framework) {
100
+ const tool = this._tools.get(name)
101
+ if (!tool) {
102
+ throw new Error(`Tool '${name}' not found`)
103
+ }
104
+
105
+ try {
106
+ this.emit('tool:call', { name, args })
107
+ const result = await tool.execute(args, framework)
108
+ this.emit('tool:result', { name, args, result })
109
+ return result
110
+ } catch (err) {
111
+ this.emit('tool:error', { name, args, error: err })
112
+ throw err
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 清空所有工具
118
+ */
119
+ clear() {
120
+ this._tools.clear()
121
+ this.emit('tool:cleared')
122
+ return this
123
+ }
124
+
125
+ /**
126
+ * 获取工具数量
127
+ * @returns {number}
128
+ */
129
+ size() {
130
+ return this._tools.size
131
+ }
132
+ }
133
+
134
+ module.exports = { ToolRegistry }