foliko 1.0.23 → 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.
@@ -40,23 +40,30 @@
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:*)",
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)"
60
67
  ]
61
68
  }
62
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
 
@@ -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,8 +81,8 @@ function parseArgs(args) {
73
81
  * Chat 命令入口
74
82
  */
75
83
  async function chatCommand(args) {
76
- const options = parseArgs(args)
77
84
 
85
+ const options = parseArgs(args)
78
86
  console.log('=== Foliko 持续对话 ===\n')
79
87
  console.log('输入 exit 或 quit 退出\n')
80
88
 
@@ -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.26",
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,7 @@ class AIPlugin extends Plugin {
38
38
  }
39
39
 
40
40
  _initAIClient() {
41
+
41
42
  if (!this.config.apiKey) {
42
43
  console.warn('[AIPlugin] No API key provided, AI features disabled')
43
44
  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)) {
@@ -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)
@@ -382,19 +393,18 @@ async function bootstrapDefaults(framework, config = {}) {
382
393
  console.log('[Bootstrap] Python Plugin Loader loaded')
383
394
  }
384
395
 
385
- // 12.6 Telegram 插件(如果配置了Token
396
+ // 12.6 Telegram 插件(默认禁用,需要在 plugins.json 中设置 enabled: true
386
397
  if (shouldLoad('telegram')) {
387
- const telegramToken = process.env.TELEGRAM_BOT_TOKEN || agentConfig.telegram?.botToken
388
- if (telegramToken) {
389
- const { Plugin } = require('../src/core/plugin-base')
390
- const createTelegramPlugin = require('./telegram-plugin')
391
- const TelegramPlugin = createTelegramPlugin(Plugin)
392
- await framework.loadPlugin(new TelegramPlugin({ botToken: telegramToken }))
393
- 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
394
403
  }
404
+ await framework.loadPlugin(new TelegramPlugin(telegramConfig))
395
405
  }
396
406
 
397
- // 12.7 WeChat 插件(始终加载,由配置控制是否扫码)
407
+ // 12.7 WeChat 插件(默认禁用,需要在 plugins.json 中设置 enabled: true)
398
408
  if (shouldLoad('weixin')) {
399
409
  try {
400
410
  const { Plugin } = require('../src/core/plugin-base')
@@ -406,7 +416,6 @@ async function bootstrapDefaults(framework, config = {}) {
406
416
  allowedUsers: agentConfig.weixin?.allowedUsers || []
407
417
  }
408
418
  await framework.loadPlugin(new WeixinPlugin(weixinConfig))
409
- console.log('[Bootstrap] WeChat Plugin loaded')
410
419
  } catch (err) {
411
420
  console.warn('[Bootstrap] WeChat Plugin not available:', err.message)
412
421
  }
@@ -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')
@@ -38,7 +40,7 @@ class SubAgentPlugin extends Plugin {
38
40
  }
39
41
 
40
42
  start(framework) {
41
- // 创建子Agent
43
+ // 创建子Agent(可能需要等待父agent准备好)
42
44
  this._createSubAgent()
43
45
 
44
46
  // 注册委托工具到主Agent(通过framework获取父agent)
@@ -54,7 +56,30 @@ class SubAgentPlugin extends Plugin {
54
56
  // 获取父Agent(主Agent)
55
57
  const parentAgent = this._getParentAgent()
56
58
  if (!parentAgent) {
57
- 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) {
58
83
  return
59
84
  }
60
85
 
@@ -184,27 +209,32 @@ class SubAgentPlugin extends Plugin {
184
209
  }
185
210
 
186
211
  /**
187
- * 注册委托工具到父Agent
212
+ * 注册委托工具到框架(会被同步到主Agent
188
213
  */
189
214
  _registerDelegateTool() {
190
- const parentAgent = this._getParentAgent()
191
- if (!parentAgent) return
215
+ const framework = this._framework
216
+ if (!framework) return
192
217
 
193
218
  const agentName = this.config.name
194
219
  const role = this.config.role
195
220
 
196
- // 注册工具到父Agent
197
- parentAgent.registerTool({
221
+ // 注册工具到框架工具注册表
222
+ framework.registerTool({
198
223
  name: agentName,
199
224
  description: `${role}:当需要${role}时,调用此工具执行任务。如:编译代码、运行测试、代码重构等。`,
200
225
  inputSchema: z.object({
201
226
  task: z.string().describe('给子Agent的具体任务描述')
202
227
  }),
203
- execute: async (args, framework) => {
228
+ execute: async (args, fw) => {
204
229
  if (!this._agent) {
205
230
  return { error: `SubAgent ${agentName} not initialized` }
206
231
  }
207
232
 
233
+ const parentAgent = fw._mainAgent
234
+ if (!parentAgent) {
235
+ return { error: `Parent agent not found` }
236
+ }
237
+
208
238
  // 发射子Agent开始处理事件
209
239
  parentAgent.emit('subagent:chat:start', {
210
240
  parentAgent,
@@ -294,11 +324,14 @@ class SubAgentManagerPlugin extends Plugin {
294
324
  this.priority = 10
295
325
 
296
326
  this.config = {
297
- agents: config.agents || [] // 子Agent配置列表
327
+ agentsDir: config.agentsDir || null, // 子Agent配置目录
328
+ agents: config.agents || [] // 子Agent配置列表
298
329
  }
299
330
 
300
331
  this._framework = null
301
332
  this._subAgents = new Map()
333
+ this._fileWatcher = null
334
+ this._lastFileStates = new Map() // 记录文件状态用于检测变化
302
335
  }
303
336
 
304
337
  install(framework) {
@@ -307,6 +340,9 @@ class SubAgentManagerPlugin extends Plugin {
307
340
  }
308
341
 
309
342
  start(framework) {
343
+ // 从 agentsDir 加载子Agent配置
344
+ this._loadAgentsFromDir()
345
+
310
346
  // 创建所有配置的子Agent
311
347
  for (const agentConfig of this.config.agents) {
312
348
  const plugin = new SubAgentPlugin(agentConfig)
@@ -315,7 +351,20 @@ class SubAgentManagerPlugin extends Plugin {
315
351
  this._subAgents.set(agentConfig.name, plugin)
316
352
  }
317
353
 
318
- // 注册管理工具
354
+ // 注册管理工具到框架工具注册表
355
+ // 这样当 agent 创建时会通过 _syncTools() 自动同步过来
356
+ this._registerTools(framework)
357
+
358
+ // 启动文件监控
359
+ this._startFileWatcher()
360
+
361
+ return this
362
+ }
363
+
364
+ /**
365
+ * 注册管理工具到框架工具注册表
366
+ */
367
+ _registerTools(framework) {
319
368
  framework.registerTool({
320
369
  name: 'subagent_list',
321
370
  description: '列出所有子Agent',
@@ -358,7 +407,251 @@ class SubAgentManagerPlugin extends Plugin {
358
407
  }
359
408
  })
360
409
 
361
- return this
410
+ framework.registerTool({
411
+ name: 'subagent_reload',
412
+ description: '重新加载子Agent配置(当新增或删除.agent/agents下的文件后使用)',
413
+ inputSchema: z.object({}),
414
+ execute: async () => {
415
+ try {
416
+ this.reload(this._framework)
417
+ return { success: true, message: '子Agent配置已重新加载' }
418
+ } catch (err) {
419
+ return { success: false, error: err.message }
420
+ }
421
+ }
422
+ })
423
+
424
+ console.log('[SubAgentManager] Management tools registered to framework')
425
+ }
426
+
427
+ /**
428
+ * 从 agentsDir 目录加载子Agent配置
429
+ */
430
+ _loadAgentsFromDir() {
431
+ const agentsDir = this.config.agentsDir
432
+ console.log('[SubAgentManager] _loadAgentsFromDir called, agentsDir:', agentsDir)
433
+ if (!agentsDir || !fs.existsSync(agentsDir)) {
434
+ console.log('[SubAgentManager] agentsDir not found or does not exist')
435
+ return
436
+ }
437
+
438
+ const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
439
+ console.log('[SubAgentManager] Found entries:', entries.length)
440
+
441
+ for (const entry of entries) {
442
+ if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
443
+ const baseName = entry.name.replace(/\.(js|json|md)$/, '')
444
+ const filePath = path.join(agentsDir, entry.name)
445
+
446
+ // 跳过与已有配置同名的
447
+ if (this.config.agents.some(a => a.name === baseName)) {
448
+ continue
449
+ }
450
+
451
+ try {
452
+ let agentConfig
453
+ if (entry.name.endsWith('.json')) {
454
+ const content = fs.readFileSync(filePath, 'utf-8')
455
+ agentConfig = JSON.parse(content)
456
+ } else if (entry.name.endsWith('.md')) {
457
+ // 解析 markdown 文件,提取 JSON 配置
458
+ agentConfig = this._parseMarkdownConfig(filePath, baseName)
459
+ console.log('[SubAgentManager] Parsed md:', baseName, agentConfig)
460
+ } else {
461
+ // 清除缓存并加载
462
+ delete require.cache[require.resolve(filePath)]
463
+ const mod = require(filePath)
464
+ agentConfig = typeof mod === 'function' ? mod() : mod
465
+ }
466
+
467
+ if (agentConfig && agentConfig.name) {
468
+ agentConfig._fromDir = true
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)
473
+ }
474
+ } catch (err) {
475
+ console.warn(`[SubAgentManager] Failed to load agent config from ${filePath}:`, err.message, err.stack)
476
+ }
477
+ }
478
+ }
479
+ }
480
+
481
+ /**
482
+ * 解析 markdown 文件中的配置
483
+ * 支持格式:
484
+ * ```json
485
+ * { "name": "xxx", "role": "xxx" }
486
+ * ```
487
+ * 或直接用 frontmatter 格式
488
+ */
489
+ _parseMarkdownConfig(filePath, defaultName) {
490
+ const content = fs.readFileSync(filePath, 'utf-8')
491
+ console.log('[SubAgentManager] _parseMarkdownConfig:', filePath)
492
+
493
+ // 尝试从 code block 中提取 JSON
494
+ const jsonMatch = content.match(/```(?:json)?\s*\n([\s\S]*?)\n\s*```/)
495
+ if (jsonMatch) {
496
+ try {
497
+ const config = JSON.parse(jsonMatch[1].trim())
498
+ console.log('[SubAgentManager] Found JSON in code block')
499
+ return config
500
+ } catch (err) {
501
+ // JSON 解析失败,继续
502
+ }
503
+ }
504
+
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 }
514
+ }
515
+ }
516
+
517
+ // 从 markdown 内容中提取配置(key: value 格式)
518
+ const config = { name: defaultName }
519
+ const yamlMatch = content.match(/^(name|role|description|parentTools|tools):\s*(.+)$/m)
520
+ if (yamlMatch) {
521
+ config[yamlMatch[1]] = yamlMatch[2].trim()
522
+ }
523
+
524
+ // 如果没有找到配置,使用文件名作为 name
525
+ return config
526
+ }
527
+
528
+ /**
529
+ * 解析类似 YAML 的配置
530
+ */
531
+ _parseYamlLike(content) {
532
+ const result = {}
533
+ const lines = content.split('\n')
534
+
535
+ for (const line of lines) {
536
+ const match = line.match(/^(\w+):\s*(.+)$/)
537
+ if (match) {
538
+ const key = match[1]
539
+ let value = match[2].trim()
540
+
541
+ // 处理数组格式 [a, b, c] 或 [a b c]
542
+ if (value.startsWith('[') && value.endsWith(']')) {
543
+ value = value.slice(1, -1).split(/[,\s]+/).filter(Boolean)
544
+ }
545
+
546
+ result[key] = value
547
+ }
548
+ }
549
+
550
+ return result
551
+ }
552
+
553
+ /**
554
+ * 启动文件监控
555
+ */
556
+ _startFileWatcher() {
557
+ const agentsDir = this.config.agentsDir
558
+ if (!agentsDir || !fs.existsSync(agentsDir)) {
559
+ return
560
+ }
561
+
562
+ // 记录初始文件状态
563
+ this._updateFileStates()
564
+
565
+ // 使用 fs.watch 监控目录变化
566
+ this._fileWatcher = fs.watch(agentsDir, { recursive: false }, (eventType, filename) => {
567
+ if (filename && (filename.endsWith('.js') || filename.endsWith('.json') || filename.endsWith('.md'))) {
568
+ console.log(`[SubAgentManager] Detected change in agents dir: ${eventType} - ${filename}`)
569
+
570
+ // 防抖:延迟处理,等文件操作完成
571
+ setTimeout(() => {
572
+ this._checkAndReload()
573
+ }, 500)
574
+ }
575
+ })
576
+
577
+ console.log(`[SubAgentManager] Watching agents dir for changes: ${agentsDir}`)
578
+ }
579
+
580
+ /**
581
+ * 更新文件状态
582
+ */
583
+ _updateFileStates() {
584
+ const agentsDir = this.config.agentsDir
585
+ if (!agentsDir || !fs.existsSync(agentsDir)) {
586
+ return
587
+ }
588
+
589
+ this._lastFileStates.clear()
590
+ const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
591
+
592
+ for (const entry of entries) {
593
+ if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
594
+ const filePath = path.join(agentsDir, entry.name)
595
+ try {
596
+ const stat = fs.statSync(filePath)
597
+ this._lastFileStates.set(entry.name, {
598
+ mtime: stat.mtime.getTime(),
599
+ size: stat.size
600
+ })
601
+ } catch (err) {
602
+ // 忽略
603
+ }
604
+ }
605
+ }
606
+ }
607
+
608
+ /**
609
+ * 检查并重载(如果有变化)
610
+ */
611
+ _checkAndReload() {
612
+ const agentsDir = this.config.agentsDir
613
+ if (!agentsDir || !fs.existsSync(agentsDir)) {
614
+ return
615
+ }
616
+
617
+ const currentStates = new Map()
618
+ const entries = fs.readdirSync(agentsDir, { withFileTypes: true })
619
+ let hasChanges = false
620
+
621
+ // 检查当前文件
622
+ for (const entry of entries) {
623
+ if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.json') || entry.name.endsWith('.md'))) {
624
+ const filePath = path.join(agentsDir, entry.name)
625
+ try {
626
+ const stat = fs.statSync(filePath)
627
+ currentStates.set(entry.name, {
628
+ mtime: stat.mtime.getTime(),
629
+ size: stat.size
630
+ })
631
+
632
+ const lastState = this._lastFileStates.get(entry.name)
633
+ if (!lastState || lastState.mtime !== stat.mtime.getTime() || lastState.size !== stat.size) {
634
+ hasChanges = true
635
+ }
636
+ } catch (err) {
637
+ // 忽略
638
+ }
639
+ }
640
+ }
641
+
642
+ // 检查删除的文件
643
+ for (const [name] of this._lastFileStates) {
644
+ if (!currentStates.has(name)) {
645
+ hasChanges = true
646
+ break
647
+ }
648
+ }
649
+
650
+ if (hasChanges) {
651
+ console.log('[SubAgentManager] File changes detected, reloading...')
652
+ this._updateFileStates()
653
+ this.reload(this._framework)
654
+ }
362
655
  }
363
656
 
364
657
  /**
@@ -370,17 +663,32 @@ class SubAgentManagerPlugin extends Plugin {
370
663
  }
371
664
 
372
665
  reload(framework) {
666
+ // 停止文件监控
667
+ if (this._fileWatcher) {
668
+ this._fileWatcher.close()
669
+ this._fileWatcher = null
670
+ }
671
+
373
672
  // 销毁旧的子Agent
374
673
  for (const plugin of this._subAgents.values()) {
375
674
  plugin.uninstall(framework)
376
675
  }
377
676
  this._subAgents.clear()
378
677
 
678
+ // 清空从目录加载的配置,重新加载
679
+ this.config.agents = this.config.agents.filter(a => !a._fromDir)
680
+
379
681
  // 重新创建
380
682
  this.start(framework)
381
683
  }
382
684
 
383
685
  uninstall(framework) {
686
+ // 停止文件监控
687
+ if (this._fileWatcher) {
688
+ this._fileWatcher.close()
689
+ this._fileWatcher = null
690
+ }
691
+
384
692
  for (const plugin of this._subAgents.values()) {
385
693
  plugin.uninstall(framework)
386
694
  }
@@ -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
- * 使用 @pinixai/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',
@@ -70,11 +72,11 @@ module.exports = function(Plugin) {
70
72
 
71
73
  let WeixinBot
72
74
  try {
73
- const module = await import('@pinixai/weixin-bot')
75
+ const module = await import('@chnak/weixin-bot')
74
76
  WeixinBot = module.WeixinBot
75
77
  } 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')
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) {