foliko 1.0.25 → 1.0.26

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.
@@ -57,7 +57,13 @@
57
57
  "Bash(node -e \"const { WeixinBot } = require\\('@chnak/weixin-bot'\\); console.log\\(typeof WeixinBot\\)\" 2>&1)",
58
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
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
+ "Bash(npm config:*)",
61
+ "Bash(node -c plugins/subagent-plugin.js 2>&1)",
62
+ "Bash(node -c plugins/default-plugins.js 2>&1)",
63
+ "Bash(node -c plugins/default-plugins.js && node -c src/core/plugin-manager.js && node -c plugins/tools-plugin.js 2>&1)",
64
+ "Bash(node -c src/core/plugin-base.js && node -c src/core/plugin-manager.js 2>&1)",
65
+ "Bash(node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)",
66
+ "Bash(node -c src/core/plugin-manager.js 2>&1)"
61
67
  ]
62
68
  }
63
69
  }
package/README.md CHANGED
@@ -11,15 +11,42 @@
11
11
  - **技能系统** - 可扩展的 Skill 管理
12
12
  - **会话管理** - 支持多会话切换
13
13
  - **规则引擎** - 可配置的行为规则
14
+ - **子 Agent** - 支持多子 Agent 分工协作
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ # 方式一:npm 安装
20
+ npm install -g foliko
21
+
22
+ # 方式二:Windows 一键脚本安装
23
+ irm https://folikoai.com/install.ps1 | iex
24
+
25
+ # 方式三:Mac/Linux 一键脚本安装
26
+ curl -fsSL https://folikoai.com/install.sh | bash
27
+ ```
14
28
 
15
29
  ## 快速开始
16
30
 
17
31
  ```bash
18
- # 安装
19
- npm install
32
+ # 全局安装后
33
+ foliko chat
34
+ ```
35
+
36
+ ## CLI 命令
37
+
38
+ ```bash
39
+ # 聊天模式(使用 .env 中的配置)
40
+ foliko chat
41
+
42
+ # 指定完整配置
43
+ foliko chat --provider deepseek --model deepseek-chat --base-url https://api.deepseek.com/v1 --api-key sk-xxx
44
+
45
+ # 只指定 provider(使用 provider 默认 model 和 baseURL)
46
+ foliko chat --provider deepseek --api-key sk-xxx
20
47
 
21
- # 启动聊天
22
- npm run chat
48
+ # 列出所有子Agent配置
49
+ foliko list
23
50
  ```
24
51
 
25
52
  ## 项目结构
@@ -30,7 +57,7 @@ foliko/
30
57
  │ └── bin/foliko.js # CLI 入口
31
58
  ├── src/ # 核心框架
32
59
  │ ├── core/ # 核心组件
33
- │ ├── capabilities/ # 能力插件
60
+ │ ├── capabilities/ # 能力插件
34
61
  │ └── executors/ # 执行器
35
62
  ├── plugins/ # 内置插件
36
63
  ├── skills/ # 技能目录
@@ -50,19 +77,19 @@ FOLIKO_PROVIDER=minimax
50
77
  # AI Model(可选,不填则使用 provider 默认值)
51
78
  # MiniMax: MiniMax-M2.7
52
79
  # DeepSeek: deepseek-chat, deepseek-coder 等
53
- FOLIKO_MODEL=
80
+ FOLIKO_MODEL=MiniMax-M2.7
54
81
 
55
82
  # API Base URL(可选,不填则使用 provider 默认值)
56
83
  # MiniMax: https://api.minimaxi.com/v1
57
84
  # DeepSeek: https://api.deepseek.com/v1
58
- FOLIKO_BASE_URL=
85
+ FOLIKO_BASE_URL=https://api.minimaxi.com/v1
59
86
 
60
87
  # API Key(通用,不填则使用 provider 专用 key)
61
- FOLIKO_API_KEY=
88
+ FOLIKO_API_KEY=sk-your-minimax-api-key
62
89
 
63
90
  # Provider 专用 API Key(可选)
64
- DEEPSEEK_API_KEY=sk-your-deepseek-api-key
65
- MINIMAX_API_KEY=sk-your-minimax-api-key
91
+ # DEEPSEEK_API_KEY=sk-your-deepseek-api-key
92
+ # MINIMAX_API_KEY=sk-your-minimax-api-key
66
93
  ```
67
94
 
68
95
  **配置优先级**:命令行参数 > .env配置 > provider默认值
@@ -74,11 +101,49 @@ MINIMAX_API_KEY=sk-your-minimax-api-key
74
101
  ├── config # 配置文件
75
102
  ├── ai.json # AI 配置
76
103
  ├── mcp_config.json # MCP 服务器配置
104
+ ├── agents/ # 子Agent配置目录
77
105
  ├── plugins/ # 用户插件目录
78
106
  ├── skills/ # 用户技能目录
79
107
  └── data/ # 数据目录
80
108
  ```
81
109
 
110
+ ### 子 Agent 配置 (.agent/agents/)
111
+
112
+ 在 `.agent/agents/` 目录下放置子 Agent 配置文件,支持 `.js`、`.json`、`.md` 格式:
113
+
114
+ ```
115
+ .agent/agents/
116
+ ├── backend-specialist.md
117
+ ├── frontend-specialist.md
118
+ └── database-architect.md
119
+ ```
120
+
121
+ #### JSON 格式
122
+
123
+ ```json
124
+ {
125
+ "name": "backend-specialist",
126
+ "role": "Backend Specialist",
127
+ "description": "Expert in backend development",
128
+ "parentTools": ["read_file", "run_command"]
129
+ }
130
+ ```
131
+
132
+ #### Markdown 格式
133
+
134
+ ```markdown
135
+ # Backend Specialist
136
+
137
+ ```json
138
+ {
139
+ "name": "backend-specialist",
140
+ "role": "Backend Specialist",
141
+ "description": "Expert in backend development",
142
+ "parentTools": ["read_file", "run_command"]
143
+ }
144
+ ```
145
+ ```
146
+
82
147
  ### ai.json 格式
83
148
 
84
149
  ```json
@@ -191,37 +256,57 @@ allowed-tools: tool1,tool2
191
256
  技能内容...
192
257
  ```
193
258
 
194
- ## CLI 命令
195
-
196
- ```bash
197
- # 聊天模式(使用 .env 中的配置)
198
- npm run chat
199
-
200
- # 指定完整配置
201
- foliko chat --provider deepseek --model deepseek-chat --base-url https://api.deepseek.com/v1 --api-key sk-xxx
202
-
203
- # 只指定 provider(使用 provider 默认 model 和 baseURL)
204
- foliko chat --provider deepseek --api-key sk-xxx
205
-
206
- # 只指定 api-key(使用 FOLIKO_PROVIDER 设定的 provider)
207
- foliko chat --api-key sk-xxx
208
- ```
209
-
210
259
  ## 内置工具
211
260
 
261
+ ### 通用工具
262
+
212
263
  | 工具 | 说明 |
213
264
  |------|------|
214
265
  | `loadSkill` | 加载技能 |
215
- | `mcp_execute` | 执行 MCP 工具 |
216
- | `shell_execute` | 执行 Shell 命令 |
217
- | `python_execute` | 执行 Python 代码 |
266
+ | `list_plugins` | 列出所有插件 |
267
+ | `list_tools` | 列出所有工具 |
268
+ | `reload_plugins` | 重载插件 |
269
+ | `shell` | 执行 Shell 命令 |
270
+ | `powershell` | 执行 PowerShell 命令 |
271
+ | `python-execute` | 执行 Python 代码 |
272
+ | `python_script` | 执行 Python 脚本 |
273
+ | `pip_install` | 安装 Python 包 |
218
274
  | `install` | 安装 npm 包 |
219
275
  | `mcp_reload` | 重载 MCP 服务器 |
220
276
  | `audit_query` | 查询审计日志 |
221
277
 
278
+ ### 子 Agent 管理工具
279
+
280
+ | 工具 | 说明 |
281
+ |------|------|
282
+ | `subagent_list` | 列出所有子Agent |
283
+ | `subagent_call` | 调用指定的子Agent处理任务 |
284
+ | `subagent_reload` | 重新加载子Agent配置 |
285
+
286
+ ### 会话工具
287
+
288
+ | 工具 | 说明 |
289
+ |------|------|
290
+ | `session_create` | 创建会话 |
291
+ | `session_get` | 获取会话 |
292
+ | `session_list` | 列出所有会话 |
293
+ | `session_delete` | 删除会话 |
294
+ | `session_history` | 获取会话历史 |
295
+
296
+ ### 定时任务工具
297
+
298
+ | 工具 | 说明 |
299
+ |------|------|
300
+ | `schedule_task` | 创建定时任务 |
301
+ | `schedule_list` | 列出定时任务 |
302
+ | `schedule_cancel` | 取消定时任务 |
303
+ | `cron_examples` | 显示 cron 示例 |
304
+
222
305
  ## 工作目录
223
306
 
224
307
  CLI 工作目录默认使用执行命令的目录。
308
+ - `.agent/` 目录会创建在工作目录下
309
+ - 配置文件、插件、技能等都放在工作目录的 `.agent/` 下
225
310
 
226
311
  ## 许可证
227
312
 
@@ -81,20 +81,8 @@ function parseArgs(args) {
81
81
  * Chat 命令入口
82
82
  */
83
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
84
 
90
85
  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
- })
97
-
98
86
  console.log('=== Foliko 持续对话 ===\n')
99
87
  console.log('输入 exit 或 quit 退出\n')
100
88
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -38,13 +38,6 @@ 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
42
  if (!this.config.apiKey) {
50
43
  console.warn('[AIPlugin] No API key provided, AI features disabled')
@@ -214,7 +214,7 @@ async function bootstrapDefaults(framework, config = {}) {
214
214
  'install', 'ai', 'storage', 'tools', 'workflow', 'skill-manager',
215
215
  'mcp-executor', 'shell-executor', 'python-executor', 'session',
216
216
  'audit', 'rules', 'scheduler', 'file-system', 'think',
217
- 'python-plugin-loader', 'telegram', 'weixin', 'subagent-manager'
217
+ 'python-plugin-loader', 'telegram', 'weixin'
218
218
  ])
219
219
 
220
220
  // 辅助函数:检查插件是否应该加载(核心插件不能禁用)
@@ -393,19 +393,18 @@ async function bootstrapDefaults(framework, config = {}) {
393
393
  console.log('[Bootstrap] Python Plugin Loader loaded')
394
394
  }
395
395
 
396
- // 12.6 Telegram 插件(如果配置了Token
396
+ // 12.6 Telegram 插件(默认禁用,需要在 plugins.json 中设置 enabled: true
397
397
  if (shouldLoad('telegram')) {
398
- const telegramToken = process.env.TELEGRAM_BOT_TOKEN || agentConfig.telegram?.botToken
399
- if (telegramToken) {
400
- const { Plugin } = require('../src/core/plugin-base')
401
- const createTelegramPlugin = require('./telegram-plugin')
402
- const TelegramPlugin = createTelegramPlugin(Plugin)
403
- await framework.loadPlugin(new TelegramPlugin({ botToken: telegramToken }))
404
- console.log('[Bootstrap] Telegram Plugin loaded')
398
+ const { Plugin } = require('../src/core/plugin-base')
399
+ const createTelegramPlugin = require('./telegram-plugin')
400
+ const TelegramPlugin = createTelegramPlugin(Plugin)
401
+ const telegramConfig = {
402
+ botToken: process.env.TELEGRAM_BOT_TOKEN || agentConfig.telegram?.botToken
405
403
  }
404
+ await framework.loadPlugin(new TelegramPlugin(telegramConfig))
406
405
  }
407
406
 
408
- // 12.7 WeChat 插件(始终加载,由配置控制是否扫码)
407
+ // 12.7 WeChat 插件(默认禁用,需要在 plugins.json 中设置 enabled: true)
409
408
  if (shouldLoad('weixin')) {
410
409
  try {
411
410
  const { Plugin } = require('../src/core/plugin-base')
@@ -417,23 +416,11 @@ async function bootstrapDefaults(framework, config = {}) {
417
416
  allowedUsers: agentConfig.weixin?.allowedUsers || []
418
417
  }
419
418
  await framework.loadPlugin(new WeixinPlugin(weixinConfig))
420
- console.log('[Bootstrap] WeChat Plugin loaded')
421
419
  } catch (err) {
422
420
  console.warn('[Bootstrap] WeChat Plugin not available:', err.message)
423
421
  }
424
422
  }
425
423
 
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
-
437
424
  // 13. 加载自定义插件
438
425
  await loadCustomPlugins(framework, agentConfig)
439
426
 
@@ -40,7 +40,7 @@ class SubAgentPlugin extends Plugin {
40
40
  }
41
41
 
42
42
  start(framework) {
43
- // 创建子Agent
43
+ // 创建子Agent(可能需要等待父agent准备好)
44
44
  this._createSubAgent()
45
45
 
46
46
  // 注册委托工具到主Agent(通过framework获取父agent)
@@ -56,7 +56,30 @@ class SubAgentPlugin extends Plugin {
56
56
  // 获取父Agent(主Agent)
57
57
  const parentAgent = this._getParentAgent()
58
58
  if (!parentAgent) {
59
- console.warn(`[SubAgent:${this.config.name}] Parent agent not found`)
59
+ // agent还没创建,监听事件等其创建
60
+ if (this._framework) {
61
+ const framework = this._framework
62
+ framework.on('agent:created', (agent) => {
63
+ if (framework._mainAgent && agent === framework._mainAgent) {
64
+ // 父agent已创建,重新尝试创建子agent
65
+ this._doCreateSubAgent(agent)
66
+ // 注册委托工具
67
+ this._registerDelegateTool()
68
+ }
69
+ })
70
+ }
71
+ return
72
+ }
73
+
74
+ this._doCreateSubAgent(parentAgent)
75
+ }
76
+
77
+ /**
78
+ * 实际执行创建子Agent
79
+ */
80
+ _doCreateSubAgent(parentAgent) {
81
+ // 避免重复创建
82
+ if (this._agent) {
60
83
  return
61
84
  }
62
85
 
@@ -186,27 +209,32 @@ class SubAgentPlugin extends Plugin {
186
209
  }
187
210
 
188
211
  /**
189
- * 注册委托工具到父Agent
212
+ * 注册委托工具到框架(会被同步到主Agent
190
213
  */
191
214
  _registerDelegateTool() {
192
- const parentAgent = this._getParentAgent()
193
- if (!parentAgent) return
215
+ const framework = this._framework
216
+ if (!framework) return
194
217
 
195
218
  const agentName = this.config.name
196
219
  const role = this.config.role
197
220
 
198
- // 注册工具到父Agent
199
- parentAgent.registerTool({
221
+ // 注册工具到框架工具注册表
222
+ framework.registerTool({
200
223
  name: agentName,
201
224
  description: `${role}:当需要${role}时,调用此工具执行任务。如:编译代码、运行测试、代码重构等。`,
202
225
  inputSchema: z.object({
203
226
  task: z.string().describe('给子Agent的具体任务描述')
204
227
  }),
205
- execute: async (args, framework) => {
228
+ execute: async (args, fw) => {
206
229
  if (!this._agent) {
207
230
  return { error: `SubAgent ${agentName} not initialized` }
208
231
  }
209
232
 
233
+ const parentAgent = fw._mainAgent
234
+ if (!parentAgent) {
235
+ return { error: `Parent agent not found` }
236
+ }
237
+
210
238
  // 发射子Agent开始处理事件
211
239
  parentAgent.emit('subagent:chat:start', {
212
240
  parentAgent,
@@ -323,11 +351,9 @@ class SubAgentManagerPlugin extends Plugin {
323
351
  this._subAgents.set(agentConfig.name, plugin)
324
352
  }
325
353
 
326
- // 注册管理工具到主 Agent,确保可用
327
- const mainAgent = framework._mainAgent
328
- if (mainAgent) {
329
- this._registerToolsToAgent(mainAgent)
330
- }
354
+ // 注册管理工具到框架工具注册表
355
+ // 这样当 agent 创建时会通过 _syncTools() 自动同步过来
356
+ this._registerTools(framework)
331
357
 
332
358
  // 启动文件监控
333
359
  this._startFileWatcher()
@@ -336,12 +362,10 @@ class SubAgentManagerPlugin extends Plugin {
336
362
  }
337
363
 
338
364
  /**
339
- * 注册管理工具到指定 Agent
365
+ * 注册管理工具到框架工具注册表
340
366
  */
341
- _registerToolsToAgent(agent) {
342
- if (!agent) return
343
-
344
- agent.registerTool({
367
+ _registerTools(framework) {
368
+ framework.registerTool({
345
369
  name: 'subagent_list',
346
370
  description: '列出所有子Agent',
347
371
  inputSchema: z.object({}),
@@ -356,7 +380,7 @@ class SubAgentManagerPlugin extends Plugin {
356
380
  }
357
381
  })
358
382
 
359
- agent.registerTool({
383
+ framework.registerTool({
360
384
  name: 'subagent_call',
361
385
  description: '调用指定的子Agent处理任务',
362
386
  inputSchema: z.object({
@@ -383,7 +407,7 @@ class SubAgentManagerPlugin extends Plugin {
383
407
  }
384
408
  })
385
409
 
386
- agent.registerTool({
410
+ framework.registerTool({
387
411
  name: 'subagent_reload',
388
412
  description: '重新加载子Agent配置(当新增或删除.agent/agents下的文件后使用)',
389
413
  inputSchema: z.object({}),
@@ -397,7 +421,7 @@ class SubAgentManagerPlugin extends Plugin {
397
421
  }
398
422
  })
399
423
 
400
- console.log('[SubAgentManager] Management tools registered to agent')
424
+ console.log('[SubAgentManager] Management tools registered to framework')
401
425
  }
402
426
 
403
427
  /**
@@ -405,11 +429,14 @@ class SubAgentManagerPlugin extends Plugin {
405
429
  */
406
430
  _loadAgentsFromDir() {
407
431
  const agentsDir = this.config.agentsDir
432
+ console.log('[SubAgentManager] _loadAgentsFromDir called, agentsDir:', agentsDir)
408
433
  if (!agentsDir || !fs.existsSync(agentsDir)) {
434
+ console.log('[SubAgentManager] agentsDir not found or does not exist')
409
435
  return
410
436
  }
411
437
 
412
438
  const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
439
+ console.log('[SubAgentManager] Found entries:', entries.length)
413
440
 
414
441
  for (const entry of entries) {
415
442
  if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
@@ -429,6 +456,7 @@ class SubAgentManagerPlugin extends Plugin {
429
456
  } else if (entry.name.endsWith('.md')) {
430
457
  // 解析 markdown 文件,提取 JSON 配置
431
458
  agentConfig = this._parseMarkdownConfig(filePath, baseName)
459
+ console.log('[SubAgentManager] Parsed md:', baseName, agentConfig)
432
460
  } else {
433
461
  // 清除缓存并加载
434
462
  delete require.cache[require.resolve(filePath)]
@@ -439,9 +467,12 @@ class SubAgentManagerPlugin extends Plugin {
439
467
  if (agentConfig && agentConfig.name) {
440
468
  agentConfig._fromDir = true
441
469
  this.config.agents.push(agentConfig)
470
+ console.log('[SubAgentManager] Loaded agent:', agentConfig.name)
471
+ } else {
472
+ console.warn('[SubAgentManager] Agent config has no name:', baseName, agentConfig)
442
473
  }
443
474
  } catch (err) {
444
- console.warn(`[SubAgentManager] Failed to load agent config from ${filePath}:`, err.message)
475
+ console.warn(`[SubAgentManager] Failed to load agent config from ${filePath}:`, err.message, err.stack)
445
476
  }
446
477
  }
447
478
  }
@@ -457,48 +488,34 @@ class SubAgentManagerPlugin extends Plugin {
457
488
  */
458
489
  _parseMarkdownConfig(filePath, defaultName) {
459
490
  const content = fs.readFileSync(filePath, 'utf-8')
491
+ console.log('[SubAgentManager] _parseMarkdownConfig:', filePath)
460
492
 
461
493
  // 尝试从 code block 中提取 JSON
462
494
  const jsonMatch = content.match(/```(?:json)?\s*\n([\s\S]*?)\n\s*```/)
463
495
  if (jsonMatch) {
464
496
  try {
465
497
  const config = JSON.parse(jsonMatch[1].trim())
498
+ console.log('[SubAgentManager] Found JSON in code block')
466
499
  return config
467
500
  } catch (err) {
468
501
  // JSON 解析失败,继续
469
502
  }
470
503
  }
471
504
 
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'
505
+ // 尝试从 frontmatter 格式中提取
506
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
507
+ if (frontmatterMatch) {
508
+ console.log('[SubAgentManager] Found frontmatter')
509
+ const frontmatterContent = frontmatterMatch[1]
510
+ const parsed = this._parseYamlLike(frontmatterContent)
511
+ if (parsed && parsed.name) {
512
+ console.log('[SubAgentManager] Parsed frontmatter:', parsed)
513
+ return { name: parsed.name || defaultName, ...parsed }
498
514
  }
499
515
  }
500
516
 
501
517
  // 从 markdown 内容中提取配置(key: value 格式)
518
+ const config = { name: defaultName }
502
519
  const yamlMatch = content.match(/^(name|role|description|parentTools|tools):\s*(.+)$/m)
503
520
  if (yamlMatch) {
504
521
  config[yamlMatch[1]] = yamlMatch[2].trim()
@@ -27,6 +27,8 @@ module.exports = function(Plugin) {
27
27
  this.version = '2.0.0'
28
28
  this.description = 'Telegram 对话插件,绑定主Agent进行持续对话'
29
29
  this.priority = 80
30
+ // 默认不启用,需要在 plugins.json 中设置 enabled: true
31
+ this.enabled = false
30
32
 
31
33
  this.config = {
32
34
  botToken: config.botToken || process.env.TELEGRAM_BOT_TOKEN,
@@ -61,18 +61,20 @@ class ToolsPlugin extends Plugin {
61
61
  }
62
62
  })
63
63
 
64
- // 列出已加载插件工具
64
+ // 列出所有插件工具(包括未加载的)
65
65
  framework.registerTool({
66
66
  name: 'list_plugins',
67
- description: '列出所有已加载的插件',
67
+ description: '列出所有插件(包括未加载的)',
68
68
  inputSchema: z.object({}),
69
69
  execute: async () => {
70
- const plugins = framework.pluginManager.getAll()
70
+ const plugins = framework.pluginManager.getAllKnown()
71
71
  return {
72
72
  success: true,
73
73
  plugins: plugins.map(p => ({
74
74
  name: p.name,
75
- version: p.instance?.version
75
+ status: p.status,
76
+ enabled: p.enabled,
77
+ version: p.version
76
78
  }))
77
79
  }
78
80
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * WeChat 插件
3
- * 使用@chnak/weixin-bot 实现微信对话
3
+ * 使用 @chnak/weixin-bot 实现微信对话
4
4
  *
5
5
  * 配置:
6
6
  * - forceLogin: 是否强制重新扫码登录
@@ -18,6 +18,8 @@ module.exports = function(Plugin) {
18
18
  this.version = '1.0.0'
19
19
  this.description = '微信对话插件,使用微信网页账号进行对话'
20
20
  this.priority = 80
21
+ // 默认不启用,需要在 plugins.json 中设置 enabled: true
22
+ this.enabled = false
21
23
 
22
24
  this.config = {
23
25
  forceLogin: config.forceLogin || process.env.WEIXIN_FORCE_LOGIN === 'true',
@@ -73,8 +75,8 @@ module.exports = function(Plugin) {
73
75
  const module = await import('@chnak/weixin-bot')
74
76
  WeixinBot = module.WeixinBot
75
77
  } catch (err) {
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
+ console.warn('[WeChat] Failed to load @chnak/weixin-bot:', err.message)
79
+ console.warn('[WeChat] Make sure @chnak/weixin-bot is installed: npm install @chnak/weixin-bot qrcode-terminal')
78
80
  return
79
81
  }
80
82
 
@@ -35,6 +35,13 @@ class Plugin {
35
35
  */
36
36
  agents = []
37
37
 
38
+ /**
39
+ * 插件是否启用,默认为 true
40
+ * 设置为 false 时插件不会被加载,但在列表中仍可见
41
+ * @type {boolean}
42
+ */
43
+ enabled = true
44
+
38
45
  _framework = null
39
46
  _subAgents = []
40
47
 
@@ -16,6 +16,7 @@ class PluginManager {
16
16
  this._plugins = new Map()
17
17
  this._loading = false
18
18
  this._stateFile = path.join(process.cwd(), '.agent', 'data', 'plugins-state.json')
19
+ this._knownPlugins = new Set() // 已知插件列表(包括未加载的)
19
20
  }
20
21
 
21
22
  /**
@@ -89,10 +90,13 @@ class PluginManager {
89
90
  pluginInstance.config = { ...pluginInstance.config, ...savedConfig }
90
91
  }
91
92
 
93
+ // 使用保存的状态,否则使用插件实例的 enabled,默认 true
94
+ const finalEnabled = savedEnabled !== undefined ? savedEnabled : (pluginInstance.enabled !== undefined ? pluginInstance.enabled : true)
95
+
92
96
  this._plugins.set(pluginInstance.name, {
93
97
  instance: pluginInstance,
94
98
  status: 'registered',
95
- enabled: savedEnabled !== undefined ? savedEnabled : true // 使用保存的状态,默认启用
99
+ enabled: finalEnabled
96
100
  })
97
101
 
98
102
  this.framework.emit('plugin:registered', pluginInstance)
@@ -116,7 +120,7 @@ class PluginManager {
116
120
  pluginInstance = this._createFromObject(plugin)
117
121
  }
118
122
 
119
- // 如果已注册,使用已注册的实例
123
+ // 如果已注册,使用已注册的实例和状态
120
124
  const existing = this._plugins.get(pluginInstance.name)
121
125
  if (existing) {
122
126
  pluginInstance = existing.instance
@@ -424,12 +428,59 @@ class PluginManager {
424
428
  .map(e => ({ name: e.instance.name, instance: e.instance }))
425
429
  }
426
430
 
431
+ /**
432
+ * 注册一个已知插件(但不加载)
433
+ * 用于显示所有可用插件列表
434
+ * @param {string} name - 插件名称
435
+ * @param {Object} info - 插件信息
436
+ */
437
+ registerKnownPlugin(name, info = {}) {
438
+ this._knownPlugins.add(name)
439
+ // 如果插件还没注册过,记录它的信息
440
+ if (!this._plugins.has(name)) {
441
+ this._plugins.set(name, {
442
+ instance: { name, ...info },
443
+ status: 'known',
444
+ enabled: info.enabled !== undefined ? info.enabled : false
445
+ })
446
+ }
447
+ }
448
+
449
+ /**
450
+ * 获取所有已知插件(包括未加载的)
451
+ * @returns {Array} 插件列表,包含 name, status, enabled
452
+ */
453
+ getAllKnown() {
454
+ const result = []
455
+ for (const name of this._knownPlugins) {
456
+ const entry = this._plugins.get(name)
457
+ result.push({
458
+ name,
459
+ status: entry?.status || 'unknown',
460
+ enabled: entry?.enabled || false,
461
+ version: entry?.instance?.version
462
+ })
463
+ }
464
+ // 也加入已加载但不在 knownPlugins 中的
465
+ for (const [name, entry] of this._plugins) {
466
+ if (!result.find(p => p.name === name)) {
467
+ result.push({
468
+ name,
469
+ status: entry.status,
470
+ enabled: entry.enabled,
471
+ version: entry.instance?.version
472
+ })
473
+ }
474
+ }
475
+ return result
476
+ }
477
+
427
478
  /**
428
479
  * 检查插件是否存在
429
480
  * @param {string} name - 插件名称
430
481
  */
431
482
  has(name) {
432
- return this._plugins.has(name)
483
+ return this._plugins.has(name) || this._knownPlugins.has(name)
433
484
  }
434
485
 
435
486
  /**
@@ -464,8 +515,12 @@ class PluginManager {
464
515
  }
465
516
 
466
517
  entry.enabled = true
518
+ // 同步更新插件实例的 enabled 属性,避免 load() 时被跳过
519
+ if (entry.instance) {
520
+ entry.instance.enabled = true
521
+ }
467
522
 
468
- // 如果插件已加载,尝试重新启动(reload 会调用 start)
523
+ // 如果插件已加载,尝试重新启动
469
524
  if (entry.status === 'loaded') {
470
525
  try {
471
526
  // 如果之前已经启动过,先调用 stop 停止旧实例
@@ -481,6 +536,13 @@ class PluginManager {
481
536
  } catch (err) {
482
537
  console.error(`[PluginManager] Enable/reload failed for '${name}':`, err.message)
483
538
  }
539
+ } else if (entry.status === 'registered' || entry.status === 'known') {
540
+ // 插件只注册过但未加载,现在加载它
541
+ try {
542
+ await this.load(entry.instance)
543
+ } catch (err) {
544
+ console.error(`[PluginManager] Enable/load failed for '${name}':`, err.message)
545
+ }
484
546
  }
485
547
 
486
548
  this.framework.emit('plugin:enabled', entry.instance)
@@ -504,6 +566,10 @@ class PluginManager {
504
566
  }
505
567
 
506
568
  entry.enabled = false
569
+ // 同步更新插件实例的 enabled 属性
570
+ if (entry.instance) {
571
+ entry.instance.enabled = false
572
+ }
507
573
 
508
574
  // 如果插件正在运行,停止它
509
575
  if (entry.instance._started) {