foliko 1.0.23 → 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.
@@ -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/@pinixai/weixin-bot && npm run build 2>&1)",
44
- "Bash(cd node_modules/@pinixai/weixin-bot && npx tsc 2>&1)",
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/@pinixai/weixin-bot && npx typescript --version && npx tsc 2>&1)",
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/@pinixai/weixin-bot && cp -r /tmp/weixin-bot/nodejs/* node_modules/@pinixai/weixin-bot/ && cd node_modules/@pinixai/weixin-bot && npm install 2>&1)",
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\\('@pinixai/weixin-bot'\\).then\\(m => console.log\\('OK:', Object.keys\\(m\\)\\)\\).catch\\(e => console.error\\('Error:', e.message\\)\\)\")",
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\\('@pinixai/weixin-bot'\\); console.log\\(typeof WeixinBot\\)\" 2>&1)",
58
- "Bash(node -e \"import\\('@pinixai/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)"
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
  }
@@ -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: process.env.FOLIKO_API_KEY || null
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
@@ -97,13 +97,33 @@ Write-Host "Cleaning npm cache..." -ForegroundColor Cyan
97
97
  npm cache clean --force 2>$null | Out-Null
98
98
 
99
99
  Write-Host "Installing Foliko..." -ForegroundColor Cyan
100
- npm install -g foliko --ignore-scripts
100
+ npm install -g foliko --ignore-scripts 2>&1 | Out-Null
101
101
 
102
- if (Get-Command foliko -ErrorAction SilentlyContinue) {
102
+ # Get npm global bin path
103
+ $npmPrefix = npm config get prefix 2>$null
104
+ $npmBinPath = $npmPrefix -replace "\\$", ""
105
+
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
115
+
116
+ # Check if installation succeeded
117
+ if (Test-Path "$npmBinPath\foliko.cmd") {
103
118
  Write-Host ""
104
119
  Write-Host "Installation complete!" -ForegroundColor Green
105
- Write-Host "Run: foliko chat"
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
106
125
  } else {
107
126
  Write-Host ""
108
- Write-Host "Installation failed. Try manually: npm install -g foliko" -ForegroundColor Yellow
127
+ Write-Host "Installation may have failed." -ForegroundColor Yellow
128
+ Write-Host "Run: npm install -g foliko"
109
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.23",
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
- "@modelcontextprotocol/sdk": "^1.27.1",
29
28
  "@chnak/weixin-bot": "^1.2.0",
29
+ "@modelcontextprotocol/sdk": "^1.27.1",
30
30
  "ai": "^6.0.116",
31
31
  "dotenv": "^17.3.1",
32
32
  "imap": "^0.8.19",
@@ -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 || process.env.DEEPSEEK_API_KEY,
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
- agents: config.agents || [] // 子Agent配置列表
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.registerTool({
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
- framework.registerTool({
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
- return this
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
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * WeChat 插件
3
- * 使用 @pinixai/weixin-bot 实现微信对话
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('@pinixai/weixin-bot')
73
+ const module = await import('@chnak/weixin-bot')
74
74
  WeixinBot = module.WeixinBot
75
75
  } catch (err) {
76
- console.warn('[WeChat] Failed to load @pinixai/weixin-bot:', err.message)
77
- console.warn('[WeChat] Make sure @pinixai/weixin-bot is installed: npm install @pinixai/weixin-bot qrcode-terminal')
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