foliko 1.0.22 → 1.0.25
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 +9 -8
- package/cli/src/commands/chat.js +21 -1
- package/cli/src/commands/list.js +90 -0
- package/cli/src/index.js +8 -0
- package/install.ps1 +29 -10
- package/package.json +2 -2
- package/plugins/ai-plugin.js +8 -0
- package/plugins/default-plugins.js +25 -3
- package/plugins/subagent-plugin.js +296 -5
- package/plugins/weixin-plugin.js +4 -4
|
@@ -40,23 +40,24 @@
|
|
|
40
40
|
"Bash(node -c plugins/telegram-plugin.js && echo \"Syntax OK\")",
|
|
41
41
|
"WebSearch",
|
|
42
42
|
"Bash(npm ls:*)",
|
|
43
|
-
"Bash(cd node_modules/@
|
|
44
|
-
"Bash(cd node_modules/@
|
|
43
|
+
"Bash(cd node_modules/@chnak/weixin-bot && npm run build 2>&1)",
|
|
44
|
+
"Bash(cd node_modules/@chnak/weixin-bot && npx tsc 2>&1)",
|
|
45
45
|
"Bash(npm install:*)",
|
|
46
|
-
"Bash(cd node_modules/@
|
|
46
|
+
"Bash(cd node_modules/@chnak/weixin-bot && npx typescript --version && npx tsc 2>&1)",
|
|
47
47
|
"Bash(npx tsc:*)",
|
|
48
48
|
"Bash(node --check /d/Code/vb-agent/plugins/weixin-plugin.js 2>&1)",
|
|
49
49
|
"Bash(node --check /d/Code/vb-agent/plugins/telegram-plugin.js && node --check /d/Code/vb-agent/plugins/weixin-plugin.js && echo \"OK\")",
|
|
50
50
|
"Bash(node --check /d/Code/vb-agent/cli/src/ui/chat-ui.js && echo \"OK\")",
|
|
51
51
|
"Bash(npm uninstall:*)",
|
|
52
52
|
"Bash(rm -rf /tmp/weixin-bot && git clone https://github.com/chnak/weixin-bot.git /tmp/weixin-bot 2>&1)",
|
|
53
|
-
"Bash(mkdir -p node_modules/@
|
|
53
|
+
"Bash(mkdir -p node_modules/@chnak/weixin-bot && cp -r /tmp/weixin-bot/nodejs/* node_modules/@chnak/weixin-bot/ && cd node_modules/@chnak/weixin-bot && npm install 2>&1)",
|
|
54
54
|
"Bash(node --check /d/Code/vb-agent/plugins/weixin-plugin.js && echo \"OK\")",
|
|
55
|
-
"Bash(node -e \"import\\('@
|
|
55
|
+
"Bash(node -e \"import\\('@chnak/weixin-bot'\\).then\\(m => console.log\\('OK:', Object.keys\\(m\\)\\)\\).catch\\(e => console.error\\('Error:', e.message\\)\\)\")",
|
|
56
56
|
"Bash(npm run:*)",
|
|
57
|
-
"Bash(node -e \"const { WeixinBot } = require\\('@
|
|
58
|
-
"Bash(node -e \"import\\('@
|
|
59
|
-
"Bash(ls -la D:/code/vb-agent/cli/bin/ && cat D:/code/vb-agent/cli/bin/*.js 2>/dev/null | head -50)"
|
|
57
|
+
"Bash(node -e \"const { WeixinBot } = require\\('@chnak/weixin-bot'\\); console.log\\(typeof WeixinBot\\)\" 2>&1)",
|
|
58
|
+
"Bash(node -e \"import\\('@chnak/weixin-bot'\\).then\\(m => console.log\\('OK:', typeof m.WeixinBot\\)\\).catch\\(e => console.error\\('Error:', e.message\\)\\)\" 2>&1)",
|
|
59
|
+
"Bash(ls -la D:/code/vb-agent/cli/bin/ && cat D:/code/vb-agent/cli/bin/*.js 2>/dev/null | head -50)",
|
|
60
|
+
"Bash(npm config:*)"
|
|
60
61
|
]
|
|
61
62
|
}
|
|
62
63
|
}
|
package/cli/src/commands/chat.js
CHANGED
|
@@ -37,11 +37,19 @@ function getEnvConfig() {
|
|
|
37
37
|
const provider = process.env.FOLIKO_PROVIDER || DEFAULT_CONFIG.provider
|
|
38
38
|
const providerDefaults = PROVIDER_DEFAULTS[provider] || PROVIDER_DEFAULTS.minimax
|
|
39
39
|
|
|
40
|
+
// 支持多种 API key 环境变量名
|
|
41
|
+
let apiKey = process.env.FOLIKO_API_KEY || null
|
|
42
|
+
if (!apiKey) {
|
|
43
|
+
// 根据 provider 查找对应的 API key
|
|
44
|
+
const upperProvider = provider.toUpperCase().replace(/-/g, '_')
|
|
45
|
+
apiKey = process.env[`${upperProvider}_API_KEY`] || null
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
return {
|
|
41
49
|
model: process.env.FOLIKO_MODEL || providerDefaults.model,
|
|
42
50
|
provider: provider,
|
|
43
51
|
baseURL: process.env.FOLIKO_BASE_URL || providerDefaults.baseURL,
|
|
44
|
-
apiKey:
|
|
52
|
+
apiKey: apiKey
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -73,7 +81,19 @@ function parseArgs(args) {
|
|
|
73
81
|
* Chat 命令入口
|
|
74
82
|
*/
|
|
75
83
|
async function chatCommand(args) {
|
|
84
|
+
console.log('[chatCommand] Starting with env:', {
|
|
85
|
+
FOLIKO_PROVIDER: process.env.FOLIKO_PROVIDER,
|
|
86
|
+
FOLIKO_MODEL: process.env.FOLIKO_MODEL,
|
|
87
|
+
MINIMAX_API_KEY: process.env.MINIMAX_API_KEY ? '***' + process.env.MINIMAX_API_KEY.slice(-4) : 'null'
|
|
88
|
+
})
|
|
89
|
+
|
|
76
90
|
const options = parseArgs(args)
|
|
91
|
+
console.log('[chatCommand] Parsed options:', {
|
|
92
|
+
provider: options.provider,
|
|
93
|
+
model: options.model,
|
|
94
|
+
baseURL: options.baseURL,
|
|
95
|
+
apiKey: options.apiKey ? '***' + options.apiKey.slice(-4) : 'null'
|
|
96
|
+
})
|
|
77
97
|
|
|
78
98
|
console.log('=== Foliko 持续对话 ===\n')
|
|
79
99
|
console.log('输入 exit 或 quit 退出\n')
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List 命令实现
|
|
3
|
+
* 列出所有子Agent配置
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const path = require('path')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 列出 .agent/agents 目录下的所有 Agent 配置
|
|
11
|
+
*/
|
|
12
|
+
async function listCommand() {
|
|
13
|
+
const agentsDir = path.resolve(process.cwd(), '.agent', 'agents')
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(agentsDir)) {
|
|
16
|
+
console.log('No .agent/agents directory found.')
|
|
17
|
+
console.log('Create agents by adding files to: .agent/agents/')
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
|
|
22
|
+
const agents = []
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
|
|
26
|
+
const baseName = entry.name.replace(/\.(js|json|md)$/, '')
|
|
27
|
+
const filePath = path.join(agentsDir, entry.name)
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
let config = { name: baseName }
|
|
31
|
+
|
|
32
|
+
if (entry.name.endsWith('.json')) {
|
|
33
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
34
|
+
config = { ...config, ...JSON.parse(content) }
|
|
35
|
+
} else if (entry.name.endsWith('.md')) {
|
|
36
|
+
// 尝试从 markdown 中提取配置
|
|
37
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
38
|
+
const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/)
|
|
39
|
+
if (jsonMatch) {
|
|
40
|
+
config = { ...config, ...JSON.parse(jsonMatch[1]) }
|
|
41
|
+
}
|
|
42
|
+
// 提取 name 行
|
|
43
|
+
const nameMatch = content.match(/^name:\s*(.+)$/m)
|
|
44
|
+
if (nameMatch) {
|
|
45
|
+
config.name = nameMatch[1].trim()
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
// .js 文件,尝试加载
|
|
49
|
+
delete require.cache[require.resolve(filePath)]
|
|
50
|
+
const mod = require(filePath)
|
|
51
|
+
config = typeof mod === 'function' ? mod() : mod
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
agents.push({
|
|
55
|
+
name: config.name || baseName,
|
|
56
|
+
role: config.role || '-',
|
|
57
|
+
description: config.description || '-',
|
|
58
|
+
file: entry.name
|
|
59
|
+
})
|
|
60
|
+
} catch (err) {
|
|
61
|
+
agents.push({
|
|
62
|
+
name: baseName,
|
|
63
|
+
role: '-',
|
|
64
|
+
description: `Error loading: ${err.message}`,
|
|
65
|
+
file: entry.name
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (agents.length === 0) {
|
|
72
|
+
console.log('No agents found in .agent/agents/')
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`\nFound ${agents.length} agent(s) in .agent/agents/:\n`)
|
|
77
|
+
console.log('Name Role Description')
|
|
78
|
+
console.log('---------------- ------------------ ------------------------------------------')
|
|
79
|
+
|
|
80
|
+
for (const agent of agents) {
|
|
81
|
+
const name = (agent.name || '').padEnd(16).slice(0, 16)
|
|
82
|
+
const role = (agent.role || '-').padEnd(18).slice(0, 18)
|
|
83
|
+
const desc = (agent.description || '-').slice(0, 40)
|
|
84
|
+
console.log(`${name} ${role} ${desc}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log('')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { listCommand }
|
package/cli/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { chatCommand } = require('./commands/chat')
|
|
6
|
+
const { listCommand } = require('./commands/list')
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* CLI 主入口
|
|
@@ -16,6 +17,11 @@ async function cli() {
|
|
|
16
17
|
await chatCommand(args.slice(1))
|
|
17
18
|
break
|
|
18
19
|
|
|
20
|
+
case 'list':
|
|
21
|
+
case 'ls':
|
|
22
|
+
await listCommand()
|
|
23
|
+
break
|
|
24
|
+
|
|
19
25
|
case 'help':
|
|
20
26
|
case '--help':
|
|
21
27
|
case '-h':
|
|
@@ -46,6 +52,7 @@ Usage: foliko <command> [options]
|
|
|
46
52
|
|
|
47
53
|
Commands:
|
|
48
54
|
chat 启动持续对话聊天
|
|
55
|
+
list 列出所有子Agent配置
|
|
49
56
|
help 显示帮助信息
|
|
50
57
|
version 显示版本号
|
|
51
58
|
|
|
@@ -58,6 +65,7 @@ Examples:
|
|
|
58
65
|
foliko chat
|
|
59
66
|
foliko chat --model MiniMax-M2.7
|
|
60
67
|
foliko chat --provider minimax --base-url https://api.minimaxi.com/v1
|
|
68
|
+
foliko list
|
|
61
69
|
`)
|
|
62
70
|
}
|
|
63
71
|
|
package/install.ps1
CHANGED
|
@@ -75,10 +75,8 @@ if (Get-Command uv -ErrorAction SilentlyContinue) {
|
|
|
75
75
|
} else {
|
|
76
76
|
Write-Host "uv not found, installing..." -ForegroundColor Yellow
|
|
77
77
|
|
|
78
|
-
Write-Host "Downloading uv..." -ForegroundColor Cyan
|
|
79
|
-
$uvInstaller = "$env:TEMP\uv-installer.pyz"
|
|
80
|
-
|
|
81
78
|
try {
|
|
79
|
+
Write-Host "Downloading uv..." -ForegroundColor Cyan
|
|
82
80
|
Invoke-WebRequest -Uri "https://astral.sh/uv/install.ps1" -OutFile "$env:TEMP\install-uv.ps1" -UseBasicParsing
|
|
83
81
|
powershell -ExecutionPolicy Bypass -File "$env:TEMP\install-uv.ps1" -Version "0.4.0" -PowerShell -Admin
|
|
84
82
|
Remove-Item "$env:TEMP\install-uv.ps1" -Force -ErrorAction SilentlyContinue
|
|
@@ -94,17 +92,38 @@ if (Get-Command uv -ErrorAction SilentlyContinue) {
|
|
|
94
92
|
}
|
|
95
93
|
Write-Host ""
|
|
96
94
|
|
|
97
|
-
# ============
|
|
98
|
-
Write-Host "
|
|
99
|
-
|
|
95
|
+
# ============ Clean npm cache and install Foliko ============
|
|
96
|
+
Write-Host "Cleaning npm cache..." -ForegroundColor Cyan
|
|
97
|
+
npm cache clean --force 2>$null | Out-Null
|
|
98
|
+
|
|
99
|
+
Write-Host "Installing Foliko..." -ForegroundColor Cyan
|
|
100
|
+
npm install -g foliko --ignore-scripts 2>&1 | Out-Null
|
|
101
|
+
|
|
102
|
+
# Get npm global bin path
|
|
103
|
+
$npmPrefix = npm config get prefix 2>$null
|
|
104
|
+
$npmBinPath = $npmPrefix -replace "\\$", ""
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
# Add to PATH permanently for current user
|
|
107
|
+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
108
|
+
if ($userPath -notlike "*$npmBinPath*") {
|
|
109
|
+
[Environment]::SetEnvironmentVariable("Path", "$userPath;$npmBinPath", "User")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Also add to current session PATH
|
|
113
|
+
$env:Path = "$npmBinPath;$env:Path"
|
|
114
|
+
Start-Sleep -Seconds 2
|
|
102
115
|
|
|
103
|
-
|
|
116
|
+
# Check if installation succeeded
|
|
117
|
+
if (Test-Path "$npmBinPath\foliko.cmd") {
|
|
104
118
|
Write-Host ""
|
|
105
119
|
Write-Host "Installation complete!" -ForegroundColor Green
|
|
106
|
-
Write-Host "
|
|
120
|
+
Write-Host ""
|
|
121
|
+
Write-Host "Please restart your terminal or run:" -ForegroundColor Cyan
|
|
122
|
+
Write-Host ' $env:Path = "C:\Users\Administrator\AppData\Roaming\npm;' + '$env:Path"' -ForegroundColor White
|
|
123
|
+
Write-Host ""
|
|
124
|
+
Write-Host "Then run: foliko chat" -ForegroundColor Yellow
|
|
107
125
|
} else {
|
|
108
126
|
Write-Host ""
|
|
109
|
-
Write-Host "Installation may have failed.
|
|
127
|
+
Write-Host "Installation may have failed." -ForegroundColor Yellow
|
|
128
|
+
Write-Host "Run: npm install -g foliko"
|
|
110
129
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "简约的插件化 Agent 框架",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"@ai-sdk/openai": "^3.0.41",
|
|
26
26
|
"@ai-sdk/openai-compatible": "^2.0.35",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
28
|
+
"@chnak/weixin-bot": "^1.2.0",
|
|
28
29
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
29
|
-
"@pinixai/weixin-bot": "https://github.com/chnak/weixin-bot.git",
|
|
30
30
|
"ai": "^6.0.116",
|
|
31
31
|
"dotenv": "^17.3.1",
|
|
32
32
|
"imap": "^0.8.19",
|
package/plugins/ai-plugin.js
CHANGED
|
@@ -38,6 +38,14 @@ class AIPlugin extends Plugin {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
_initAIClient() {
|
|
41
|
+
console.log('[AIPlugin] initAIClient called')
|
|
42
|
+
console.log('[AIPlugin] config:', JSON.stringify({
|
|
43
|
+
provider: this.config.provider,
|
|
44
|
+
model: this.config.model,
|
|
45
|
+
apiKey: this.config.apiKey ? '***' + this.config.apiKey.slice(-4) : 'null',
|
|
46
|
+
baseURL: this.config.baseURL
|
|
47
|
+
}))
|
|
48
|
+
|
|
41
49
|
if (!this.config.apiKey) {
|
|
42
50
|
console.warn('[AIPlugin] No API key provided, AI features disabled')
|
|
43
51
|
return
|
|
@@ -17,7 +17,8 @@ function loadAgentConfig(agentDir = '.agent') {
|
|
|
17
17
|
ai: {},
|
|
18
18
|
mcpServers: {},
|
|
19
19
|
plugins: [],
|
|
20
|
-
skillsDirs: []
|
|
20
|
+
skillsDirs: [],
|
|
21
|
+
agentsDir: null // 子Agent配置目录
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const resolvedDir = path.resolve(process.cwd(), agentDir)
|
|
@@ -29,6 +30,13 @@ function loadAgentConfig(agentDir = '.agent') {
|
|
|
29
30
|
|
|
30
31
|
console.log(`[AgentConfig] Loading config from: ${resolvedDir}`)
|
|
31
32
|
|
|
33
|
+
// 添加 agents 目录(如果存在)
|
|
34
|
+
const agentsDir = path.join(resolvedDir, 'agents')
|
|
35
|
+
if (fs.existsSync(agentsDir)) {
|
|
36
|
+
config.agentsDir = agentsDir
|
|
37
|
+
console.log(`[AgentConfig] Found agents directory: ${agentsDir}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
// 加载 config 文件(key=value 格式)
|
|
33
41
|
const configFile = path.join(resolvedDir, 'config')
|
|
34
42
|
if (fs.existsSync(configFile)) {
|
|
@@ -206,7 +214,7 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
206
214
|
'install', 'ai', 'storage', 'tools', 'workflow', 'skill-manager',
|
|
207
215
|
'mcp-executor', 'shell-executor', 'python-executor', 'session',
|
|
208
216
|
'audit', 'rules', 'scheduler', 'file-system', 'think',
|
|
209
|
-
'python-plugin-loader', 'telegram', 'weixin'
|
|
217
|
+
'python-plugin-loader', 'telegram', 'weixin', 'subagent-manager'
|
|
210
218
|
])
|
|
211
219
|
|
|
212
220
|
// 辅助函数:检查插件是否应该加载(核心插件不能禁用)
|
|
@@ -242,10 +250,13 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
242
250
|
// 跳过或已禁用
|
|
243
251
|
} else {
|
|
244
252
|
const { AIPlugin } = require('./ai-plugin')
|
|
253
|
+
// 根据 provider 获取对应的 API key
|
|
254
|
+
const upperProvider = (aiConfig.provider || 'deepseek').toUpperCase().replace(/-/g, '_')
|
|
255
|
+
const envApiKey = process.env[`${upperProvider}_API_KEY`] || process.env.FOLIKO_API_KEY
|
|
245
256
|
const aiPlugin = new AIPlugin({
|
|
246
257
|
provider: aiConfig.provider || 'deepseek',
|
|
247
258
|
model: aiConfig.model || 'deepseek-chat',
|
|
248
|
-
apiKey: aiConfig.apiKey ||
|
|
259
|
+
apiKey: aiConfig.apiKey || envApiKey,
|
|
249
260
|
baseURL: aiConfig.baseURL
|
|
250
261
|
})
|
|
251
262
|
await framework.loadPlugin(aiPlugin)
|
|
@@ -412,6 +423,17 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
412
423
|
}
|
|
413
424
|
}
|
|
414
425
|
|
|
426
|
+
// 12.8 SubAgent 管理器(加载 .agent/agents 目录下的子Agent)
|
|
427
|
+
if (shouldLoad('subagent-manager')) {
|
|
428
|
+
try {
|
|
429
|
+
const { SubAgentManagerPlugin } = require('./subagent-plugin')
|
|
430
|
+
await framework.loadPlugin(new SubAgentManagerPlugin({ agentsDir: agentConfig.agentsDir }))
|
|
431
|
+
console.log('[Bootstrap] SubAgent Manager loaded')
|
|
432
|
+
} catch (err) {
|
|
433
|
+
console.warn('[Bootstrap] SubAgent Manager failed:', err.message)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
415
437
|
// 13. 加载自定义插件
|
|
416
438
|
await loadCustomPlugins(framework, agentConfig)
|
|
417
439
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* 创建具有独立工具集的子Agent,用于分工处理不同领域的任务
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const path = require('path')
|
|
6
8
|
const { Plugin } = require('../src/core/plugin-base')
|
|
7
9
|
const { z } = require('zod')
|
|
8
10
|
const { Agent } = require('../src/core/agent')
|
|
@@ -294,11 +296,14 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
294
296
|
this.priority = 10
|
|
295
297
|
|
|
296
298
|
this.config = {
|
|
297
|
-
|
|
299
|
+
agentsDir: config.agentsDir || null, // 子Agent配置目录
|
|
300
|
+
agents: config.agents || [] // 子Agent配置列表
|
|
298
301
|
}
|
|
299
302
|
|
|
300
303
|
this._framework = null
|
|
301
304
|
this._subAgents = new Map()
|
|
305
|
+
this._fileWatcher = null
|
|
306
|
+
this._lastFileStates = new Map() // 记录文件状态用于检测变化
|
|
302
307
|
}
|
|
303
308
|
|
|
304
309
|
install(framework) {
|
|
@@ -307,6 +312,9 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
307
312
|
}
|
|
308
313
|
|
|
309
314
|
start(framework) {
|
|
315
|
+
// 从 agentsDir 加载子Agent配置
|
|
316
|
+
this._loadAgentsFromDir()
|
|
317
|
+
|
|
310
318
|
// 创建所有配置的子Agent
|
|
311
319
|
for (const agentConfig of this.config.agents) {
|
|
312
320
|
const plugin = new SubAgentPlugin(agentConfig)
|
|
@@ -315,8 +323,25 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
315
323
|
this._subAgents.set(agentConfig.name, plugin)
|
|
316
324
|
}
|
|
317
325
|
|
|
318
|
-
//
|
|
319
|
-
framework.
|
|
326
|
+
// 注册管理工具到主 Agent,确保可用
|
|
327
|
+
const mainAgent = framework._mainAgent
|
|
328
|
+
if (mainAgent) {
|
|
329
|
+
this._registerToolsToAgent(mainAgent)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 启动文件监控
|
|
333
|
+
this._startFileWatcher()
|
|
334
|
+
|
|
335
|
+
return this
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 注册管理工具到指定 Agent
|
|
340
|
+
*/
|
|
341
|
+
_registerToolsToAgent(agent) {
|
|
342
|
+
if (!agent) return
|
|
343
|
+
|
|
344
|
+
agent.registerTool({
|
|
320
345
|
name: 'subagent_list',
|
|
321
346
|
description: '列出所有子Agent',
|
|
322
347
|
inputSchema: z.object({}),
|
|
@@ -331,7 +356,7 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
331
356
|
}
|
|
332
357
|
})
|
|
333
358
|
|
|
334
|
-
|
|
359
|
+
agent.registerTool({
|
|
335
360
|
name: 'subagent_call',
|
|
336
361
|
description: '调用指定的子Agent处理任务',
|
|
337
362
|
inputSchema: z.object({
|
|
@@ -358,7 +383,258 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
358
383
|
}
|
|
359
384
|
})
|
|
360
385
|
|
|
361
|
-
|
|
386
|
+
agent.registerTool({
|
|
387
|
+
name: 'subagent_reload',
|
|
388
|
+
description: '重新加载子Agent配置(当新增或删除.agent/agents下的文件后使用)',
|
|
389
|
+
inputSchema: z.object({}),
|
|
390
|
+
execute: async () => {
|
|
391
|
+
try {
|
|
392
|
+
this.reload(this._framework)
|
|
393
|
+
return { success: true, message: '子Agent配置已重新加载' }
|
|
394
|
+
} catch (err) {
|
|
395
|
+
return { success: false, error: err.message }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
console.log('[SubAgentManager] Management tools registered to agent')
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* 从 agentsDir 目录加载子Agent配置
|
|
405
|
+
*/
|
|
406
|
+
_loadAgentsFromDir() {
|
|
407
|
+
const agentsDir = this.config.agentsDir
|
|
408
|
+
if (!agentsDir || !fs.existsSync(agentsDir)) {
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
|
|
413
|
+
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
|
|
416
|
+
const baseName = entry.name.replace(/\.(js|json|md)$/, '')
|
|
417
|
+
const filePath = path.join(agentsDir, entry.name)
|
|
418
|
+
|
|
419
|
+
// 跳过与已有配置同名的
|
|
420
|
+
if (this.config.agents.some(a => a.name === baseName)) {
|
|
421
|
+
continue
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
let agentConfig
|
|
426
|
+
if (entry.name.endsWith('.json')) {
|
|
427
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
428
|
+
agentConfig = JSON.parse(content)
|
|
429
|
+
} else if (entry.name.endsWith('.md')) {
|
|
430
|
+
// 解析 markdown 文件,提取 JSON 配置
|
|
431
|
+
agentConfig = this._parseMarkdownConfig(filePath, baseName)
|
|
432
|
+
} else {
|
|
433
|
+
// 清除缓存并加载
|
|
434
|
+
delete require.cache[require.resolve(filePath)]
|
|
435
|
+
const mod = require(filePath)
|
|
436
|
+
agentConfig = typeof mod === 'function' ? mod() : mod
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (agentConfig && agentConfig.name) {
|
|
440
|
+
agentConfig._fromDir = true
|
|
441
|
+
this.config.agents.push(agentConfig)
|
|
442
|
+
}
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.warn(`[SubAgentManager] Failed to load agent config from ${filePath}:`, err.message)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 解析 markdown 文件中的配置
|
|
452
|
+
* 支持格式:
|
|
453
|
+
* ```json
|
|
454
|
+
* { "name": "xxx", "role": "xxx" }
|
|
455
|
+
* ```
|
|
456
|
+
* 或直接用 frontmatter 格式
|
|
457
|
+
*/
|
|
458
|
+
_parseMarkdownConfig(filePath, defaultName) {
|
|
459
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
460
|
+
|
|
461
|
+
// 尝试从 code block 中提取 JSON
|
|
462
|
+
const jsonMatch = content.match(/```(?:json)?\s*\n([\s\S]*?)\n\s*```/)
|
|
463
|
+
if (jsonMatch) {
|
|
464
|
+
try {
|
|
465
|
+
const config = JSON.parse(jsonMatch[1].trim())
|
|
466
|
+
return config
|
|
467
|
+
} catch (err) {
|
|
468
|
+
// JSON 解析失败,继续
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// 尝试从 content 部分提取配置
|
|
473
|
+
// 格式:key: value
|
|
474
|
+
const config = { name: defaultName }
|
|
475
|
+
const lines = content.split('\n')
|
|
476
|
+
let inFrontmatter = false
|
|
477
|
+
let frontmatterContent = ''
|
|
478
|
+
|
|
479
|
+
for (const line of lines) {
|
|
480
|
+
// 检查是否在 frontmatter 中
|
|
481
|
+
if (line.trim() === '---') {
|
|
482
|
+
if (inFrontmatter) {
|
|
483
|
+
// 解析 frontmatter
|
|
484
|
+
try {
|
|
485
|
+
const parsed = this._parseYamlLike(frontmatterContent)
|
|
486
|
+
return { ...config, ...parsed }
|
|
487
|
+
} catch (err) {
|
|
488
|
+
// 解析失败
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
inFrontmatter = true
|
|
492
|
+
frontmatterContent = ''
|
|
493
|
+
continue
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (inFrontmatter) {
|
|
497
|
+
frontmatterContent += line + '\n'
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// 从 markdown 内容中提取配置(key: value 格式)
|
|
502
|
+
const yamlMatch = content.match(/^(name|role|description|parentTools|tools):\s*(.+)$/m)
|
|
503
|
+
if (yamlMatch) {
|
|
504
|
+
config[yamlMatch[1]] = yamlMatch[2].trim()
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// 如果没有找到配置,使用文件名作为 name
|
|
508
|
+
return config
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 解析类似 YAML 的配置
|
|
513
|
+
*/
|
|
514
|
+
_parseYamlLike(content) {
|
|
515
|
+
const result = {}
|
|
516
|
+
const lines = content.split('\n')
|
|
517
|
+
|
|
518
|
+
for (const line of lines) {
|
|
519
|
+
const match = line.match(/^(\w+):\s*(.+)$/)
|
|
520
|
+
if (match) {
|
|
521
|
+
const key = match[1]
|
|
522
|
+
let value = match[2].trim()
|
|
523
|
+
|
|
524
|
+
// 处理数组格式 [a, b, c] 或 [a b c]
|
|
525
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
526
|
+
value = value.slice(1, -1).split(/[,\s]+/).filter(Boolean)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
result[key] = value
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return result
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* 启动文件监控
|
|
538
|
+
*/
|
|
539
|
+
_startFileWatcher() {
|
|
540
|
+
const agentsDir = this.config.agentsDir
|
|
541
|
+
if (!agentsDir || !fs.existsSync(agentsDir)) {
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// 记录初始文件状态
|
|
546
|
+
this._updateFileStates()
|
|
547
|
+
|
|
548
|
+
// 使用 fs.watch 监控目录变化
|
|
549
|
+
this._fileWatcher = fs.watch(agentsDir, { recursive: false }, (eventType, filename) => {
|
|
550
|
+
if (filename && (filename.endsWith('.js') || filename.endsWith('.json') || filename.endsWith('.md'))) {
|
|
551
|
+
console.log(`[SubAgentManager] Detected change in agents dir: ${eventType} - ${filename}`)
|
|
552
|
+
|
|
553
|
+
// 防抖:延迟处理,等文件操作完成
|
|
554
|
+
setTimeout(() => {
|
|
555
|
+
this._checkAndReload()
|
|
556
|
+
}, 500)
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
console.log(`[SubAgentManager] Watching agents dir for changes: ${agentsDir}`)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* 更新文件状态
|
|
565
|
+
*/
|
|
566
|
+
_updateFileStates() {
|
|
567
|
+
const agentsDir = this.config.agentsDir
|
|
568
|
+
if (!agentsDir || !fs.existsSync(agentsDir)) {
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
this._lastFileStates.clear()
|
|
573
|
+
const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
|
|
574
|
+
|
|
575
|
+
for (const entry of entries) {
|
|
576
|
+
if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
|
|
577
|
+
const filePath = path.join(agentsDir, entry.name)
|
|
578
|
+
try {
|
|
579
|
+
const stat = fs.statSync(filePath)
|
|
580
|
+
this._lastFileStates.set(entry.name, {
|
|
581
|
+
mtime: stat.mtime.getTime(),
|
|
582
|
+
size: stat.size
|
|
583
|
+
})
|
|
584
|
+
} catch (err) {
|
|
585
|
+
// 忽略
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* 检查并重载(如果有变化)
|
|
593
|
+
*/
|
|
594
|
+
_checkAndReload() {
|
|
595
|
+
const agentsDir = this.config.agentsDir
|
|
596
|
+
if (!agentsDir || !fs.existsSync(agentsDir)) {
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const currentStates = new Map()
|
|
601
|
+
const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
|
|
602
|
+
let hasChanges = false
|
|
603
|
+
|
|
604
|
+
// 检查当前文件
|
|
605
|
+
for (const entry of entries) {
|
|
606
|
+
if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
|
|
607
|
+
const filePath = path.join(agentsDir, entry.name)
|
|
608
|
+
try {
|
|
609
|
+
const stat = fs.statSync(filePath)
|
|
610
|
+
currentStates.set(entry.name, {
|
|
611
|
+
mtime: stat.mtime.getTime(),
|
|
612
|
+
size: stat.size
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
const lastState = this._lastFileStates.get(entry.name)
|
|
616
|
+
if (!lastState || lastState.mtime !== stat.mtime.getTime() || lastState.size !== stat.size) {
|
|
617
|
+
hasChanges = true
|
|
618
|
+
}
|
|
619
|
+
} catch (err) {
|
|
620
|
+
// 忽略
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// 检查删除的文件
|
|
626
|
+
for (const [name] of this._lastFileStates) {
|
|
627
|
+
if (!currentStates.has(name)) {
|
|
628
|
+
hasChanges = true
|
|
629
|
+
break
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (hasChanges) {
|
|
634
|
+
console.log('[SubAgentManager] File changes detected, reloading...')
|
|
635
|
+
this._updateFileStates()
|
|
636
|
+
this.reload(this._framework)
|
|
637
|
+
}
|
|
362
638
|
}
|
|
363
639
|
|
|
364
640
|
/**
|
|
@@ -370,17 +646,32 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
370
646
|
}
|
|
371
647
|
|
|
372
648
|
reload(framework) {
|
|
649
|
+
// 停止文件监控
|
|
650
|
+
if (this._fileWatcher) {
|
|
651
|
+
this._fileWatcher.close()
|
|
652
|
+
this._fileWatcher = null
|
|
653
|
+
}
|
|
654
|
+
|
|
373
655
|
// 销毁旧的子Agent
|
|
374
656
|
for (const plugin of this._subAgents.values()) {
|
|
375
657
|
plugin.uninstall(framework)
|
|
376
658
|
}
|
|
377
659
|
this._subAgents.clear()
|
|
378
660
|
|
|
661
|
+
// 清空从目录加载的配置,重新加载
|
|
662
|
+
this.config.agents = this.config.agents.filter(a => !a._fromDir)
|
|
663
|
+
|
|
379
664
|
// 重新创建
|
|
380
665
|
this.start(framework)
|
|
381
666
|
}
|
|
382
667
|
|
|
383
668
|
uninstall(framework) {
|
|
669
|
+
// 停止文件监控
|
|
670
|
+
if (this._fileWatcher) {
|
|
671
|
+
this._fileWatcher.close()
|
|
672
|
+
this._fileWatcher = null
|
|
673
|
+
}
|
|
674
|
+
|
|
384
675
|
for (const plugin of this._subAgents.values()) {
|
|
385
676
|
plugin.uninstall(framework)
|
|
386
677
|
}
|
package/plugins/weixin-plugin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WeChat 插件
|
|
3
|
-
*
|
|
3
|
+
* 使用@chnak/weixin-bot 实现微信对话
|
|
4
4
|
*
|
|
5
5
|
* 配置:
|
|
6
6
|
* - forceLogin: 是否强制重新扫码登录
|
|
@@ -70,11 +70,11 @@ module.exports = function(Plugin) {
|
|
|
70
70
|
|
|
71
71
|
let WeixinBot
|
|
72
72
|
try {
|
|
73
|
-
const module = await import('@
|
|
73
|
+
const module = await import('@chnak/weixin-bot')
|
|
74
74
|
WeixinBot = module.WeixinBot
|
|
75
75
|
} catch (err) {
|
|
76
|
-
console.warn('[WeChat] Failed to load
|
|
77
|
-
console.warn('[WeChat] Make sure
|
|
76
|
+
console.warn('[WeChat] Failed to load@chnak/weixin-bot:', err.message)
|
|
77
|
+
console.warn('[WeChat] Make sure@chnak/weixin-bot is installed: npm install@chnak/weixin-bot qrcode-terminal')
|
|
78
78
|
return
|
|
79
79
|
}
|
|
80
80
|
|