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.
- package/.claude/settings.local.json +30 -0
- package/22.txt +10 -0
- package/README.md +218 -0
- package/SPEC.md +452 -0
- package/cli/bin/foliko.js +12 -0
- package/cli/src/commands/chat.js +75 -0
- package/cli/src/index.js +64 -0
- package/cli/src/ui/chat-ui.js +272 -0
- package/cli/src/utils/ansi.js +40 -0
- package/cli/src/utils/markdown.js +296 -0
- package/docs/quick-reference.md +131 -0
- package/docs/user-manual.md +1205 -0
- package/examples/basic.js +110 -0
- package/examples/bootstrap.js +93 -0
- package/examples/mcp-example.js +53 -0
- package/examples/skill-example.js +49 -0
- package/examples/workflow.js +158 -0
- package/package.json +36 -0
- package/plugins/ai-plugin.js +89 -0
- package/plugins/audit-plugin.js +187 -0
- package/plugins/default-plugins.js +412 -0
- package/plugins/file-system-plugin.js +344 -0
- package/plugins/install-plugin.js +93 -0
- package/plugins/python-executor-plugin.js +331 -0
- package/plugins/rules-plugin.js +292 -0
- package/plugins/scheduler-plugin.js +426 -0
- package/plugins/session-plugin.js +343 -0
- package/plugins/shell-executor-plugin.js +196 -0
- package/plugins/storage-plugin.js +237 -0
- package/plugins/subagent-plugin.js +395 -0
- package/plugins/think-plugin.js +329 -0
- package/plugins/tools-plugin.js +114 -0
- package/skills/mcp-usage/SKILL.md +198 -0
- package/skills/vb-agent-dev/AGENTS.md +162 -0
- package/skills/vb-agent-dev/SKILL.md +370 -0
- package/src/capabilities/index.js +11 -0
- package/src/capabilities/skill-manager.js +319 -0
- package/src/capabilities/workflow-engine.js +401 -0
- package/src/core/agent-chat.js +311 -0
- package/src/core/agent.js +573 -0
- package/src/core/framework.js +255 -0
- package/src/core/index.js +19 -0
- package/src/core/plugin-base.js +205 -0
- package/src/core/plugin-manager.js +392 -0
- package/src/core/provider.js +108 -0
- package/src/core/tool-registry.js +134 -0
- package/src/core/tool-router.js +216 -0
- package/src/executors/executor-base.js +58 -0
- package/src/executors/mcp-executor.js +728 -0
- package/src/index.js +37 -0
- package/src/utils/event-emitter.js +97 -0
- package/test-chat.js +129 -0
- package/test-mcp.js +79 -0
- package/test-reload.js +61 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit 审计日志插件
|
|
3
|
+
* 记录所有操作日志,支持日志查询和统计
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Plugin } = require('../src/core/plugin-base')
|
|
7
|
+
const { z } = require('zod')
|
|
8
|
+
|
|
9
|
+
class AuditPlugin extends Plugin {
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
super()
|
|
12
|
+
this.name = 'audit'
|
|
13
|
+
this.version = '1.0.0'
|
|
14
|
+
this.description = '审计日志插件,记录工具调用、技能执行、错误信息等操作历史'
|
|
15
|
+
this.priority = 20
|
|
16
|
+
|
|
17
|
+
this.config = {
|
|
18
|
+
maxLogs: config.maxLogs || 1000,
|
|
19
|
+
retentionDays: config.retentionDays || 30,
|
|
20
|
+
logDir: config.logDir || '.agent/logs'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this._framework = null
|
|
24
|
+
this._logs = []
|
|
25
|
+
this._logIdCounter = 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
install(framework) {
|
|
29
|
+
this._framework = framework
|
|
30
|
+
|
|
31
|
+
// 监听框架事件
|
|
32
|
+
framework.on('tool:call', (data) => this._log('tool_call', data))
|
|
33
|
+
framework.on('tool:result', (data) => this._log('tool_result', data))
|
|
34
|
+
framework.on('tool:error', (data) => this._log('tool_error', data))
|
|
35
|
+
framework.on('agent:message', (data) => this._log('agent_message', data))
|
|
36
|
+
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
start(framework) {
|
|
41
|
+
// 注册审计查询工具
|
|
42
|
+
framework.registerTool({
|
|
43
|
+
name: 'audit_query',
|
|
44
|
+
description: '查询审计日志',
|
|
45
|
+
inputSchema: z.object({
|
|
46
|
+
type: z.string().optional().describe('日志类型: tool_call, tool_result, tool_error, agent_message'),
|
|
47
|
+
limit: z.number().optional().describe('返回数量限制,默认 50'),
|
|
48
|
+
offset: z.number().optional().describe('偏移量,默认 0')
|
|
49
|
+
}),
|
|
50
|
+
execute: async (args) => {
|
|
51
|
+
let logs = this._logs
|
|
52
|
+
|
|
53
|
+
if (args.type) {
|
|
54
|
+
logs = logs.filter(log => log.type === args.type)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const offset = args.offset || 0
|
|
58
|
+
const limit = args.limit || 50
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
logs: logs.slice(-limit - offset, logs.length - offset),
|
|
63
|
+
total: logs.length,
|
|
64
|
+
offset,
|
|
65
|
+
limit
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
framework.registerTool({
|
|
71
|
+
name: 'audit_stats',
|
|
72
|
+
description: '获取审计统计信息',
|
|
73
|
+
inputSchema: z.object({}),
|
|
74
|
+
execute: async () => {
|
|
75
|
+
const stats = this.getStats()
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
stats
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
framework.registerTool({
|
|
84
|
+
name: 'audit_export',
|
|
85
|
+
description: '导出审计日志',
|
|
86
|
+
inputSchema: z.object({
|
|
87
|
+
format: z.string().optional().describe('导出格式: json (默认), csv'),
|
|
88
|
+
type: z.string().optional().describe('日志类型过滤'),
|
|
89
|
+
startDate: z.string().optional().describe('开始日期 ISO 格式'),
|
|
90
|
+
endDate: z.string().optional().describe('结束日期 ISO 格式')
|
|
91
|
+
}),
|
|
92
|
+
execute: async (args) => {
|
|
93
|
+
const result = this.exportLogs(args)
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
count: result.logs.length,
|
|
97
|
+
data: result.logs
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return this
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 记录日志
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
_log(type, data) {
|
|
110
|
+
const log = {
|
|
111
|
+
id: ++this._logIdCounter,
|
|
112
|
+
type,
|
|
113
|
+
data,
|
|
114
|
+
timestamp: new Date()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this._logs.push(log)
|
|
118
|
+
|
|
119
|
+
// 限制日志数量
|
|
120
|
+
if (this._logs.length > this.config.maxLogs) {
|
|
121
|
+
this._logs = this._logs.slice(-this.config.maxLogs)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return log
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 获取统计信息
|
|
129
|
+
*/
|
|
130
|
+
getStats() {
|
|
131
|
+
const counts = {}
|
|
132
|
+
for (const log of this._logs) {
|
|
133
|
+
counts[log.type] = (counts[log.type] || 0) + 1
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
total: this._logs.length,
|
|
138
|
+
maxLogs: this.config.maxLogs,
|
|
139
|
+
counts,
|
|
140
|
+
oldest: this._logs.length > 0 ? this._logs[0].timestamp : null,
|
|
141
|
+
newest: this._logs.length > 0 ? this._logs[this._logs.length - 1].timestamp : null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 导出日志
|
|
147
|
+
*/
|
|
148
|
+
exportLogs(options = {}) {
|
|
149
|
+
let logs = [...this._logs]
|
|
150
|
+
|
|
151
|
+
// 按类型过滤
|
|
152
|
+
if (options.type) {
|
|
153
|
+
logs = logs.filter(log => log.type === options.type)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 按日期过滤
|
|
157
|
+
if (options.startDate) {
|
|
158
|
+
const start = new Date(options.startDate)
|
|
159
|
+
logs = logs.filter(log => log.timestamp >= start)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options.endDate) {
|
|
163
|
+
const end = new Date(options.endDate)
|
|
164
|
+
logs = logs.filter(log => log.timestamp <= end)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { logs }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 手动记录日志
|
|
172
|
+
*/
|
|
173
|
+
log(type, data) {
|
|
174
|
+
return this._log(type, data)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
reload(framework) {
|
|
178
|
+
this._framework = framework
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
uninstall(framework) {
|
|
182
|
+
this._logs = []
|
|
183
|
+
this._framework = null
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = { AuditPlugin }
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 默认插件配置加载器
|
|
3
|
+
* 检测 .agent/ 目录下的配置,提供给 bootstrap() 使用
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const path = require('path')
|
|
8
|
+
const { Plugin } = require('../src/core/plugin-base')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 加载 .agent 目录下的配置
|
|
12
|
+
* 返回配置对象,可用于手动加载插件
|
|
13
|
+
*/
|
|
14
|
+
function loadAgentConfig(agentDir = '.agent') {
|
|
15
|
+
const config = {
|
|
16
|
+
agentDir,
|
|
17
|
+
ai: {},
|
|
18
|
+
mcpServers: {},
|
|
19
|
+
plugins: [],
|
|
20
|
+
skillsDirs: []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const resolvedDir = path.resolve(process.cwd(), agentDir)
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
26
|
+
console.log(`[AgentConfig] .agent directory not found: ${resolvedDir}`)
|
|
27
|
+
return config
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`[AgentConfig] Loading config from: ${resolvedDir}`)
|
|
31
|
+
|
|
32
|
+
// 加载 config 文件(key=value 格式)
|
|
33
|
+
const configFile = path.join(resolvedDir, 'config')
|
|
34
|
+
if (fs.existsSync(configFile)) {
|
|
35
|
+
try {
|
|
36
|
+
const lines = fs.readFileSync(configFile, 'utf-8').split('\n')
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const trimmed = line.trim()
|
|
39
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
40
|
+
|
|
41
|
+
const colonIndex = trimmed.indexOf(':')
|
|
42
|
+
if (colonIndex === -1) continue
|
|
43
|
+
|
|
44
|
+
const key = trimmed.substring(0, colonIndex).trim()
|
|
45
|
+
const value = trimmed.substring(colonIndex + 1).trim()
|
|
46
|
+
|
|
47
|
+
if (key === 'ai_key' || key === 'api_key') {
|
|
48
|
+
config.ai.apiKey = value
|
|
49
|
+
} else if (key === 'ai_model' || key === 'model') {
|
|
50
|
+
config.ai.model = value
|
|
51
|
+
} else if (key === 'ai_provider' || key === 'provider') {
|
|
52
|
+
config.ai.provider = value
|
|
53
|
+
} else if (key === 'ai_base_url' || key === 'baseURL') {
|
|
54
|
+
config.ai.baseURL = value
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(`[AgentConfig] Failed to load config:`, err.message)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 加载 ai.json
|
|
63
|
+
const aiFile = path.join(resolvedDir, 'ai.json')
|
|
64
|
+
if (fs.existsSync(aiFile)) {
|
|
65
|
+
try {
|
|
66
|
+
const content = fs.readFileSync(aiFile, 'utf-8')
|
|
67
|
+
const aiConfig = JSON.parse(content)
|
|
68
|
+
config.ai = { ...config.ai, ...aiConfig }
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(`[AgentConfig] Failed to load ai.json:`, err.message)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 加载 plugins.json
|
|
75
|
+
const pluginsFile = path.join(resolvedDir, 'plugins.json')
|
|
76
|
+
if (fs.existsSync(pluginsFile)) {
|
|
77
|
+
try {
|
|
78
|
+
const content = fs.readFileSync(pluginsFile, 'utf-8')
|
|
79
|
+
config.plugins = JSON.parse(content)
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(`[AgentConfig] Failed to load plugins.json:`, err.message)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 加载 mcp_config.json
|
|
86
|
+
const mcpFile = path.join(resolvedDir, 'mcp_config.json')
|
|
87
|
+
if (fs.existsSync(mcpFile)) {
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(mcpFile, 'utf-8')
|
|
90
|
+
const mcpConfig = JSON.parse(content)
|
|
91
|
+
config.mcpServers = mcpConfig.mcpServers || {}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error(`[AgentConfig] Failed to load mcp_config.json:`, err.message)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 添加 .agent/skills 目录(不存在则创建)
|
|
98
|
+
const skillsDir = path.join(resolvedDir, 'skills')
|
|
99
|
+
if (fs.existsSync(resolvedDir) && !fs.existsSync(skillsDir)) {
|
|
100
|
+
fs.mkdirSync(skillsDir, { recursive: true })
|
|
101
|
+
console.log(`[AgentConfig] Created skills directory: ${skillsDir}`)
|
|
102
|
+
}
|
|
103
|
+
if (fs.existsSync(skillsDir)) {
|
|
104
|
+
config.skillsDirs.push(skillsDir)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 添加 agentDir 父目录的 skills/ 目录
|
|
108
|
+
const parentDir = path.dirname(__dirname)
|
|
109
|
+
const rootSkillsDir = path.join(parentDir, 'skills')
|
|
110
|
+
if (fs.existsSync(rootSkillsDir)) {
|
|
111
|
+
config.skillsDirs.push(rootSkillsDir)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// const defaultSkillsDir = path.join(process.cwd(), 'defaultSkills')
|
|
115
|
+
// if (fs.existsSync(defaultSkillsDir)) {
|
|
116
|
+
// config.skillsDirs.push(defaultSkillsDir)
|
|
117
|
+
// }
|
|
118
|
+
|
|
119
|
+
return config
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* DefaultPlugins - 默认插件配置加载器
|
|
124
|
+
*/
|
|
125
|
+
class DefaultPlugins extends Plugin {
|
|
126
|
+
constructor(config = {}) {
|
|
127
|
+
super()
|
|
128
|
+
this.name = 'defaults'
|
|
129
|
+
this.version = '1.0.0'
|
|
130
|
+
this.description = '默认插件配置加载器'
|
|
131
|
+
this.priority = 0 // 最先加载
|
|
132
|
+
|
|
133
|
+
this._framework = null
|
|
134
|
+
this._agentDir = config.agentDir || '.agent'
|
|
135
|
+
this._config = null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
install(framework) {
|
|
139
|
+
this._framework = framework
|
|
140
|
+
return this
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
start(framework) {
|
|
144
|
+
// 加载配置
|
|
145
|
+
this._config = loadAgentConfig(this._agentDir)
|
|
146
|
+
return this
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 获取加载的配置
|
|
151
|
+
*/
|
|
152
|
+
getConfig() {
|
|
153
|
+
return this._config || loadAgentConfig(this._agentDir)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
reload(framework) {
|
|
157
|
+
this._framework = framework
|
|
158
|
+
this._config = loadAgentConfig(this._agentDir)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
uninstall(framework) {
|
|
162
|
+
this._framework = null
|
|
163
|
+
this._config = null
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Bootstrap 辅助函数
|
|
169
|
+
* 根据配置加载所有默认插件
|
|
170
|
+
*/
|
|
171
|
+
async function bootstrapDefaults(framework, config = {}) {
|
|
172
|
+
// 如果已经有配置,使用现有配置;否则加载
|
|
173
|
+
const agentConfig = config._config
|
|
174
|
+
if (!agentConfig) {
|
|
175
|
+
console.error('[Bootstrap] No config provided, skipping plugin loading')
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 设置 bootstrap 模式,避免重复启动
|
|
180
|
+
framework.pluginManager.setBootstrapping(true)
|
|
181
|
+
|
|
182
|
+
// 合并 AI 配置
|
|
183
|
+
const aiConfig = { ...agentConfig.ai, ...config.aiConfig }
|
|
184
|
+
|
|
185
|
+
// 0. Install 工具插件(最先加载,让其他插件能用到它的 node_modules)
|
|
186
|
+
if (!framework.pluginManager.has('install')) {
|
|
187
|
+
const { InstallPlugin } = require('./install-plugin')
|
|
188
|
+
await framework.loadPlugin(new InstallPlugin({ agentDir: agentConfig.agentDir }))
|
|
189
|
+
console.log('[Bootstrap] Install Plugin loaded')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 合并 skillsDirs 配置(bootstrap 传入的优先)
|
|
193
|
+
const skillsDirs = [
|
|
194
|
+
...(config.skillsDirs || []),
|
|
195
|
+
...(agentConfig.skillsDirs || [])
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
console.log('[Bootstrap] Loading default plugins...')
|
|
199
|
+
|
|
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) {
|
|
204
|
+
const { AIPlugin } = require('./ai-plugin')
|
|
205
|
+
const aiPlugin = new AIPlugin({
|
|
206
|
+
provider: aiConfig.provider || 'deepseek',
|
|
207
|
+
model: aiConfig.model || 'deepseek-chat',
|
|
208
|
+
apiKey: aiConfig.apiKey || process.env.DEEPSEEK_API_KEY,
|
|
209
|
+
baseURL: aiConfig.baseURL
|
|
210
|
+
})
|
|
211
|
+
await framework.loadPlugin(aiPlugin)
|
|
212
|
+
console.log('[Bootstrap] AI Plugin loaded')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 2. Storage 存储插件
|
|
216
|
+
if (!framework.pluginManager.has('storage')) {
|
|
217
|
+
const { StoragePlugin } = require('./storage-plugin')
|
|
218
|
+
await framework.loadPlugin(new StoragePlugin())
|
|
219
|
+
console.log('[Bootstrap] Storage Plugin loaded')
|
|
220
|
+
} else {
|
|
221
|
+
console.log('[Bootstrap] Storage Plugin already loaded, skipping')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 3. 内置工具插件
|
|
225
|
+
if (!framework.pluginManager.has('tools')) {
|
|
226
|
+
const { ToolsPlugin } = require('./tools-plugin')
|
|
227
|
+
await framework.loadPlugin(new ToolsPlugin())
|
|
228
|
+
console.log('[Bootstrap] Tools Plugin loaded')
|
|
229
|
+
} else {
|
|
230
|
+
console.log('[Bootstrap] Tools Plugin already loaded, skipping')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 4. 工作流插件
|
|
234
|
+
if (!framework.pluginManager.has('workflow')) {
|
|
235
|
+
const { WorkflowPlugin } = require('../src/capabilities/workflow-engine')
|
|
236
|
+
await framework.loadPlugin(new WorkflowPlugin())
|
|
237
|
+
console.log('[Bootstrap] Workflow Plugin loaded')
|
|
238
|
+
} else {
|
|
239
|
+
console.log('[Bootstrap] Workflow Plugin already loaded, skipping')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 5. Skill 管理器插件
|
|
243
|
+
if (!framework.pluginManager.has('skill-manager')) {
|
|
244
|
+
if (skillsDirs.length > 0) {
|
|
245
|
+
const { SkillManagerPlugin } = require('../src/capabilities/skill-manager')
|
|
246
|
+
// 传递所有 skills 目录
|
|
247
|
+
await framework.loadPlugin(new SkillManagerPlugin({ skillsDirs }))
|
|
248
|
+
console.log('[Bootstrap] Skill Manager loaded')
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
console.log('[Bootstrap] Skill Manager already loaded, skipping')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 6. MCP 执行器插件(始终加载,确保 mcp_reload 工具可用)
|
|
255
|
+
if (!framework.pluginManager.has('mcp-executor')) {
|
|
256
|
+
const mcpServers = Object.entries(agentConfig.mcpServers || {})
|
|
257
|
+
const servers = mcpServers.map(([name, cfg]) => ({
|
|
258
|
+
...cfg,
|
|
259
|
+
name,
|
|
260
|
+
command: cfg.command,
|
|
261
|
+
args: cfg.args || [],
|
|
262
|
+
env: cfg.env || {},
|
|
263
|
+
url: cfg.url,
|
|
264
|
+
headers: cfg.headers
|
|
265
|
+
}))
|
|
266
|
+
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
267
|
+
await framework.loadPlugin(new MCPExecutorPlugin({ servers }))
|
|
268
|
+
console.log(`[Bootstrap] MCP Executor loaded${servers.length > 0 ? ` (${servers.length} servers)` : ' (no servers)'}`)
|
|
269
|
+
} else {
|
|
270
|
+
console.log('[Bootstrap] MCP Executor already loaded, skipping')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 7. Shell 执行器插件
|
|
274
|
+
if (!framework.pluginManager.has('shell-executor')) {
|
|
275
|
+
const { ShellExecutorPlugin } = require('./shell-executor-plugin')
|
|
276
|
+
await framework.loadPlugin(new ShellExecutorPlugin())
|
|
277
|
+
console.log('[Bootstrap] Shell Executor loaded')
|
|
278
|
+
} else {
|
|
279
|
+
console.log('[Bootstrap] Shell Executor already loaded, skipping')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 8. Python 执行器插件
|
|
283
|
+
if (!framework.pluginManager.has('python-executor')) {
|
|
284
|
+
const { PythonExecutorPlugin } = require('./python-executor-plugin')
|
|
285
|
+
await framework.loadPlugin(new PythonExecutorPlugin())
|
|
286
|
+
console.log('[Bootstrap] Python Executor loaded')
|
|
287
|
+
} else {
|
|
288
|
+
console.log('[Bootstrap] Python Executor already loaded, skipping')
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 9. Session 会话管理插件
|
|
292
|
+
if (!framework.pluginManager.has('session')) {
|
|
293
|
+
const { SessionPlugin } = require('./session-plugin')
|
|
294
|
+
await framework.loadPlugin(new SessionPlugin())
|
|
295
|
+
console.log('[Bootstrap] Session Plugin loaded')
|
|
296
|
+
} else {
|
|
297
|
+
console.log('[Bootstrap] Session Plugin already loaded, skipping')
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 10. Audit 审计日志插件
|
|
301
|
+
if (!framework.pluginManager.has('audit')) {
|
|
302
|
+
const { AuditPlugin } = require('./audit-plugin')
|
|
303
|
+
await framework.loadPlugin(new AuditPlugin())
|
|
304
|
+
console.log('[Bootstrap] Audit Plugin loaded')
|
|
305
|
+
} else {
|
|
306
|
+
console.log('[Bootstrap] Audit Plugin already loaded, skipping')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 10. Rules 规则引擎插件
|
|
310
|
+
if (!framework.pluginManager.has('rules')) {
|
|
311
|
+
const { RulesPlugin } = require('./rules-plugin')
|
|
312
|
+
await framework.loadPlugin(new RulesPlugin())
|
|
313
|
+
console.log('[Bootstrap] Rules Plugin loaded')
|
|
314
|
+
} else {
|
|
315
|
+
console.log('[Bootstrap] Rules Plugin already loaded, skipping')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 11. Scheduler 定时任务插件
|
|
319
|
+
if (!framework.pluginManager.has('scheduler')) {
|
|
320
|
+
const { SchedulerPlugin } = require('./scheduler-plugin')
|
|
321
|
+
await framework.loadPlugin(new SchedulerPlugin())
|
|
322
|
+
console.log('[Bootstrap] Scheduler Plugin loaded')
|
|
323
|
+
} else {
|
|
324
|
+
console.log('[Bootstrap] Scheduler Plugin already loaded, skipping')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 11. FileSystem 文件系统插件
|
|
328
|
+
if (!framework.pluginManager.has('file-system')) {
|
|
329
|
+
const { FileSystemPlugin } = require('./file-system-plugin')
|
|
330
|
+
await framework.loadPlugin(new FileSystemPlugin())
|
|
331
|
+
console.log('[Bootstrap] FileSystem Plugin loaded')
|
|
332
|
+
} else {
|
|
333
|
+
console.log('[Bootstrap] FileSystem Plugin already loaded, skipping')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 12. Think 主动思考插件
|
|
337
|
+
if (!framework.pluginManager.has('think')) {
|
|
338
|
+
const { ThinkPlugin } = require('./think-plugin')
|
|
339
|
+
await framework.loadPlugin(new ThinkPlugin())
|
|
340
|
+
console.log('[Bootstrap] Think Plugin loaded')
|
|
341
|
+
} else {
|
|
342
|
+
console.log('[Bootstrap] Think Plugin already loaded, skipping')
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// 13. 加载自定义插件
|
|
346
|
+
await loadCustomPlugins(framework, agentConfig)
|
|
347
|
+
|
|
348
|
+
console.log('[Bootstrap] All plugins loaded')
|
|
349
|
+
|
|
350
|
+
// 统一启动所有插件(避免重复启动)
|
|
351
|
+
await framework.pluginManager.startAll()
|
|
352
|
+
|
|
353
|
+
// 清除 bootstrap 模式
|
|
354
|
+
framework.pluginManager.setBootstrapping(false)
|
|
355
|
+
|
|
356
|
+
console.log('[Bootstrap] Plugins:', framework.pluginManager.getAll().map(p => p.name))
|
|
357
|
+
console.log('[Bootstrap] Tools:', framework.getTools().map(t => t.name))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function loadCustomPlugins(framework, agentConfig) {
|
|
361
|
+
// 加载 .agent/plugins 目录下的自定义插件
|
|
362
|
+
const pluginsDir = path.resolve(process.cwd(), '.agent', 'plugins')
|
|
363
|
+
|
|
364
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.js'))
|
|
369
|
+
|
|
370
|
+
for (const file of files) {
|
|
371
|
+
try {
|
|
372
|
+
const pluginPath = path.join(pluginsDir, file)
|
|
373
|
+
|
|
374
|
+
// 清除缓存
|
|
375
|
+
delete require.cache[require.resolve(pluginPath)]
|
|
376
|
+
const pluginModule = require(pluginPath)
|
|
377
|
+
|
|
378
|
+
let plugin
|
|
379
|
+
if (typeof pluginModule === 'function') {
|
|
380
|
+
// 免引入写法:直接传递函数,让 loadPlugin 内部处理
|
|
381
|
+
plugin = pluginModule
|
|
382
|
+
} else if (pluginModule.default) {
|
|
383
|
+
plugin = pluginModule.default
|
|
384
|
+
} else {
|
|
385
|
+
plugin = pluginModule
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 获取插件名称
|
|
389
|
+
const tempPlugin = typeof plugin === 'function' && plugin.prototype?.name
|
|
390
|
+
? new plugin() : plugin
|
|
391
|
+
const pluginName = tempPlugin.name || file.replace('.js', '')
|
|
392
|
+
|
|
393
|
+
// 如果插件已加载且已启动,跳过
|
|
394
|
+
if (framework.pluginManager.has(pluginName) &&
|
|
395
|
+
framework.pluginManager.get(pluginName)?._started) {
|
|
396
|
+
continue
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log(`[Bootstrap] Loading custom plugin: ${file}`)
|
|
400
|
+
await framework.loadPlugin(plugin)
|
|
401
|
+
} catch (err) {
|
|
402
|
+
console.error(`[Bootstrap] Failed to load plugin ${file}:`, err.message)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
module.exports = {
|
|
408
|
+
DefaultPlugins,
|
|
409
|
+
loadAgentConfig,
|
|
410
|
+
bootstrapDefaults,
|
|
411
|
+
loadCustomPlugins
|
|
412
|
+
}
|