foliko 1.1.88 → 1.1.90

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/.env.example CHANGED
@@ -1,56 +1,56 @@
1
- # ========== AI Configuration ==========
2
- # 最大输出 tokens(影响 AI 回复长度)
3
- MAX_OUTPUT_TOKENS=8192
4
- # AI Provider: minimax, deepseek, openai, anthropic 等
5
- FOLIKO_PROVIDER=minimax
6
-
7
- # AI Model(如果未设置,使用 provider 默认值)
8
- # MiniMax: MiniMax-M2.7
9
- # DeepSeek: deepseek-chat, deepseek-coder 等
10
- FOLIKO_MODEL=MiniMax-M2.7
11
-
12
- # API Base URL(如果未设置,使用 provider 默认值)
13
- # MiniMax: https://api.minimaxi.com/v1
14
- # DeepSeek: https://api.deepseek.com/v1
15
- FOLIKO_BASE_URL=https://api.minimaxi.com/v1
16
-
17
- # API Key(通用,如果未设置则尝试 provider 专用 key)
18
- FOLIKO_API_KEY=sk-your-api-key
19
-
20
- # Provider 专用 API Key(可选,如果 FOLIKO_API_KEY 未设置则使用这些)
21
- DEEPSEEK_API_KEY=sk-your-deepseek-api-key
22
- MINIMAX_API_KEY=sk-your-minimax-api-key
23
-
24
- # ========== Email Configuration ==========
25
- # SMTP Settings (for sending emails)
26
- SMTP_HOST=smtp.gmail.com
27
- SMTP_PORT=587
28
- SMTP_SECURE=false
29
- SMTP_USER=your-email@gmail.com
30
- SMTP_PASS=your-app-password
31
-
32
- # IMAP Settings (for reading emails)
33
- IMAP_HOST=imap.gmail.com
34
- IMAP_PORT=993
35
- IMAP_USER=your-email@gmail.com
36
- IMAP_PASS=your-app-password
37
-
38
- # Default sender email address
39
- FROM_EMAIL=your-email@gmail.com
40
-
41
- # ========== Telegram Bot (optional) ==========
42
- TELEGRAM_BOT_TOKEN=your-telegram-bot-token
43
-
44
- # ========== Feishu Bot (optional) ==========
45
- FEISHU_APP_ID=cli_xxxxxxxxxxx
46
- FEISHU_APP_SECRET=app_secret
47
-
48
- # ========== Web Server (optional) ==========
49
- # Web 服务端口,默认 8088
50
- WEB_PORT=3000
51
-
52
- # Web 服务主机,默认 127.0.0.1
53
- WEB_HOST=127.0.0.1
54
-
55
- # 公网访问的 base URL(用于生成 webhook URL 等),不设置则使用 host:port,最好部署在docker中可以暴露自定义域名
56
- WEB_BASE_URL=https://your-domain.com
1
+ # ========== AI Configuration ==========
2
+ # 最大输出 tokens(影响 AI 回复长度)
3
+ MAX_OUTPUT_TOKENS=8192
4
+ # AI Provider: minimax, deepseek, openai, anthropic 等
5
+ FOLIKO_PROVIDER=minimax
6
+
7
+ # AI Model(如果未设置,使用 provider 默认值)
8
+ # MiniMax: MiniMax-M2.7
9
+ # DeepSeek: deepseek-chat, deepseek-coder 等
10
+ FOLIKO_MODEL=MiniMax-M2.7
11
+
12
+ # API Base URL(如果未设置,使用 provider 默认值)
13
+ # MiniMax: https://api.minimaxi.com/v1
14
+ # DeepSeek: https://api.deepseek.com/v1
15
+ FOLIKO_BASE_URL=https://api.minimaxi.com/v1
16
+
17
+ # API Key(通用,如果未设置则尝试 provider 专用 key)
18
+ FOLIKO_API_KEY=sk-your-api-key
19
+
20
+ # Provider 专用 API Key(可选,如果 FOLIKO_API_KEY 未设置则使用这些)
21
+ DEEPSEEK_API_KEY=sk-your-deepseek-api-key
22
+ MINIMAX_API_KEY=sk-your-minimax-api-key
23
+
24
+ # ========== Email Configuration ==========
25
+ # SMTP Settings (for sending emails)
26
+ SMTP_HOST=smtp.gmail.com
27
+ SMTP_PORT=587
28
+ SMTP_SECURE=false
29
+ SMTP_USER=your-email@gmail.com
30
+ SMTP_PASS=your-app-password
31
+
32
+ # IMAP Settings (for reading emails)
33
+ IMAP_HOST=imap.gmail.com
34
+ IMAP_PORT=993
35
+ IMAP_USER=your-email@gmail.com
36
+ IMAP_PASS=your-app-password
37
+
38
+ # Default sender email address
39
+ FROM_EMAIL=your-email@gmail.com
40
+
41
+ # ========== Telegram Bot (optional) ==========
42
+ TELEGRAM_BOT_TOKEN=your-telegram-bot-token
43
+
44
+ # ========== Feishu Bot (optional) ==========
45
+ FEISHU_APP_ID=cli_xxxxxxxxxxx
46
+ FEISHU_APP_SECRET=app_secret
47
+
48
+ # ========== Web Server (optional) ==========
49
+ # Web 服务端口,默认 8088
50
+ WEB_PORT=3000
51
+
52
+ # Web 服务主机,默认 127.0.0.1
53
+ WEB_HOST=127.0.0.1
54
+
55
+ # 公网访问的 base URL(用于生成 webhook URL 等),不设置则使用 host:port,最好部署在docker中可以暴露自定义域名
56
+ WEB_BASE_URL=https://your-domain.com
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.88",
3
+ "version": "1.1.90",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -81,7 +81,7 @@
81
81
  "node-schedule": "^2.1.1",
82
82
  "node-telegram-bot-api": "^0.67.0",
83
83
  "nodemailer": "^6.10.0",
84
- "pino": "^10.3.1",
84
+ "pino": "7",
85
85
  "pino-pretty": "^13.1.3",
86
86
  "qrcode-terminal": "^0.12.0",
87
87
  "remove-markdown": "^0.6.3",
@@ -14,7 +14,13 @@ const bootstrapLog = logger.child('Bootstrap')
14
14
  * 加载 .foliko 目录下的配置
15
15
  * 返回配置对象,可用于手动加载插件
16
16
  */
17
- function loadAgentConfig(agentDir = '.foliko') {
17
+ function loadAgentConfig(framework, agentDir = '.foliko') {
18
+ // 向后兼容:旧调用 loadAgentConfig('.foliko') 时把 framework 当成 agentDir
19
+ if (typeof framework === 'string') {
20
+ agentDir = framework;
21
+ framework = null;
22
+ }
23
+
18
24
  const config = {
19
25
  agentDir,
20
26
  ai: {},
@@ -24,13 +30,9 @@ function loadAgentConfig(agentDir = '.foliko') {
24
30
  agentsDir: null, // 子Agent配置目录
25
31
  pluginLinks: {} // 外部插件链接 { "插件名": "外部路径" }
26
32
  }
27
- const cmdDir=path.resolve(process.cwd())
28
- const resolvedDir = path.resolve(process.cwd(), agentDir)
29
-
30
- if (!fs.existsSync(resolvedDir)) {
31
- //log.info(` .foliko directory not found: ${resolvedDir}`)
32
- return config
33
- }
33
+ const cwd = framework?.getCwd?.() ?? process.cwd()
34
+ const cmdDir = path.resolve(cwd)
35
+ const resolvedDir = path.resolve(cwd, agentDir)
34
36
 
35
37
  //log.info(` Loading config from: ${resolvedDir}`)
36
38
 
@@ -146,10 +148,10 @@ function loadAgentConfig(agentDir = '.foliko') {
146
148
 
147
149
  // 添加 .foliko/skills 目录(不存在则创建)
148
150
  const cmdskillsDir = path.join(cmdDir, 'skills')
149
- if (fs.existsSync(resolvedDir) && !fs.existsSync(cmdskillsDir)) {
150
- fs.mkdirSync(cmdskillsDir, { recursive: true })
151
- //log.info(` Created skills directory: ${cmdskillsDir}`)
152
- }
151
+ // if (fs.existsSync(resolvedDir) && !fs.existsSync(cmdskillsDir)) {
152
+ // fs.mkdirSync(cmdskillsDir, { recursive: true })
153
+ // //log.info(` Created skills directory: ${cmdskillsDir}`)
154
+ // }
153
155
  if (fs.existsSync(cmdskillsDir)) {
154
156
  config.skillsDirs.push(cmdskillsDir)
155
157
  }
@@ -167,6 +169,114 @@ function loadAgentConfig(agentDir = '.foliko') {
167
169
  // config.skillsDirs.push(defaultSkillsDir)
168
170
  // }
169
171
 
172
+ // ============ HOME DIR (~/.foliko/) SCAN ============
173
+ // Home 是独立配置源:
174
+ // - 单文件配置:项目不存在时 home 作为回退
175
+ // - 多文件资源(skills/agents/plugins):两个目录都扫描
176
+ const homeDir = framework?.getHomeAgentDir?.()
177
+ const isHomeSameAsProject = homeDir && path.resolve(homeDir) === resolvedDir
178
+ if (homeDir && fs.existsSync(homeDir) && !isHomeSameAsProject) {
179
+ // 单文件配置回退
180
+ const homeConfig = path.join(homeDir, 'config')
181
+ if (!fs.existsSync(path.join(resolvedDir, 'config')) && fs.existsSync(homeConfig)) {
182
+ try {
183
+ const lines = fs.readFileSync(homeConfig, 'utf-8').split('\n')
184
+ for (const line of lines) {
185
+ const trimmed = line.trim()
186
+ if (!trimmed || trimmed.startsWith('#')) continue
187
+ const colonIndex = trimmed.indexOf(':')
188
+ if (colonIndex === -1) continue
189
+ const key = trimmed.substring(0, colonIndex).trim()
190
+ const value = trimmed.substring(colonIndex + 1).trim()
191
+ if (key === 'ai_key' || key === 'api_key') {
192
+ if (!config.ai.apiKey) config.ai.apiKey = value
193
+ } else if (key === 'ai_model' || key === 'model') {
194
+ if (!config.ai.model) config.ai.model = value
195
+ } else if (key === 'ai_provider' || key === 'provider') {
196
+ if (!config.ai.provider) config.ai.provider = value
197
+ } else if (key === 'ai_base_url' || key === 'baseURL') {
198
+ if (!config.ai.baseURL) config.ai.baseURL = value
199
+ }
200
+ }
201
+ } catch (err) {
202
+ log.error(' Failed to load home config:', err.message)
203
+ }
204
+ }
205
+
206
+ const homeAi = path.join(homeDir, 'ai.json')
207
+ if (!fs.existsSync(path.join(resolvedDir, 'ai.json')) && fs.existsSync(homeAi)) {
208
+ try {
209
+ const homeObj = JSON.parse(fs.readFileSync(homeAi, 'utf-8'))
210
+ config.ai = { ...homeObj, ...config.ai }
211
+ } catch (err) {
212
+ log.error(' Failed to load home ai.json:', err.message)
213
+ }
214
+ }
215
+
216
+ const homePluginsFile = path.join(homeDir, 'plugins.json')
217
+ if (!fs.existsSync(path.join(resolvedDir, 'plugins.json')) && fs.existsSync(homePluginsFile)) {
218
+ try {
219
+ const pc = JSON.parse(fs.readFileSync(homePluginsFile, 'utf-8'))
220
+ if (pc.telegram && !config.telegram) config.telegram = pc.telegram
221
+ if (pc.weixin && !config.weixin) config.weixin = pc.weixin
222
+ if (pc.email && !config.email) config.email = pc.email
223
+ config.pluginLinks = { ...(pc.pluginLinks || {}), ...config.pluginLinks }
224
+ } catch (err) {
225
+ log.error(' Failed to load home plugins.json:', err.message)
226
+ }
227
+ }
228
+
229
+ const homeWeixin = path.join(homeDir, 'weixin.json')
230
+ if (!fs.existsSync(path.join(resolvedDir, 'weixin.json')) && fs.existsSync(homeWeixin)) {
231
+ try {
232
+ const w = JSON.parse(fs.readFileSync(homeWeixin, 'utf-8'))
233
+ config.weixin = { ...w, ...config.weixin }
234
+ } catch (err) {
235
+ log.error(' Failed to load home weixin.json:', err.message)
236
+ }
237
+ }
238
+
239
+ const homeMcp = path.join(homeDir, 'mcp_config.json')
240
+ if (!fs.existsSync(path.join(resolvedDir, 'mcp_config.json')) && fs.existsSync(homeMcp)) {
241
+ try {
242
+ const m = JSON.parse(fs.readFileSync(homeMcp, 'utf-8'))
243
+ config.mcpServers = { ...(m.mcpServers || {}), ...config.mcpServers }
244
+ } catch (err) {
245
+ log.error(' Failed to load home mcp_config.json:', err.message)
246
+ }
247
+ }
248
+
249
+ // agents 目录:home 仅在项目无 agentsDir 时生效
250
+ const homeAgentsDir = path.join(homeDir, 'agents')
251
+ if (!config.agentsDir && fs.existsSync(homeAgentsDir)) {
252
+ config.agentsDir = homeAgentsDir
253
+ }
254
+
255
+ // skills 目录:项目 .foliko/skills 之后插入 home
256
+ const homeSkillsDir = path.join(homeDir, 'skills')
257
+ if (!fs.existsSync(homeSkillsDir)) {
258
+ try { fs.mkdirSync(homeSkillsDir, { recursive: true }) } catch (err) { /* ignore */ }
259
+ }
260
+ if (fs.existsSync(homeSkillsDir)) {
261
+ const projectFolikoSkillsIdx = config.skillsDirs.findIndex(
262
+ d => d === path.join(resolvedDir, 'skills')
263
+ )
264
+ const insertAt = projectFolikoSkillsIdx >= 0
265
+ ? projectFolikoSkillsIdx + 1
266
+ : config.skillsDirs.length
267
+ config.skillsDirs.splice(insertAt, 0, homeSkillsDir)
268
+ }
269
+
270
+ // home plugins:扫描并加入 pluginLinks
271
+ const homePluginsDir = path.join(homeDir, 'plugins')
272
+ if (fs.existsSync(homePluginsDir)) {
273
+ for (const name of scanPluginNames(homePluginsDir)) {
274
+ const resolvedPlugin = resolvePluginPath(homePluginsDir, name)
275
+ if (resolvedPlugin) config.pluginLinks[name] = resolvedPlugin.path
276
+ }
277
+ }
278
+ }
279
+
170
280
  return config
171
281
  }
172
282
 
@@ -194,7 +304,7 @@ class DefaultPlugins extends Plugin {
194
304
 
195
305
  start(framework) {
196
306
  // 加载配置
197
- this._config = loadAgentConfig(this._agentDir)
307
+ this._config = loadAgentConfig(this._framework, this._agentDir)
198
308
  return this
199
309
  }
200
310
 
@@ -202,12 +312,12 @@ class DefaultPlugins extends Plugin {
202
312
  * 获取加载的配置
203
313
  */
204
314
  getConfig() {
205
- return this._config || loadAgentConfig(this._agentDir)
315
+ return this._config || loadAgentConfig(this._framework, this._agentDir)
206
316
  }
207
317
 
208
318
  reload(framework) {
209
319
  this._framework = framework
210
- this._config = loadAgentConfig(this._agentDir)
320
+ this._config = loadAgentConfig(this._framework, this._agentDir)
211
321
  }
212
322
 
213
323
  uninstall(framework) {
@@ -510,15 +620,17 @@ function scanPluginNames(pluginsDir) {
510
620
  async function loadCustomPlugins(framework, agentConfig) {
511
621
  const { Plugin } = require('../src/core/plugin-base')
512
622
 
623
+ const cwd = framework?.getCwd?.() ?? process.cwd()
624
+
513
625
  // 加载两个目录下的自定义插件:
514
626
  // 1. .foliko/plugins/ - 用户自定义插件(强制启用)
515
627
  // 2. plugins/ - 项目内置插件(自动加载)
516
628
  const dirs = [
517
- { dir: path.resolve(process.cwd(), '.foliko', 'plugins'), forceEnabled: true },
518
- { dir: path.resolve(process.cwd(), 'plugins'), forceEnabled: false },
519
- { dir: path.resolve(__dirname, '..', 'plugins'), forceEnabled: false }
629
+ { dir: path.resolve(cwd, '.foliko', 'plugins'), forceEnabled: true },
630
+ { dir: path.resolve(cwd, 'plugins'), forceEnabled: false },
631
+ { dir: path.resolve(__dirname, '..', 'plugins'), forceEnabled: false }
520
632
  // 项目下的 plugins 目录(兼容旧版本)
521
-
633
+
522
634
  ]
523
635
 
524
636
  for (const { dir, forceEnabled } of dirs) {
@@ -262,7 +262,7 @@ class FeishuPlugin extends Plugin {
262
262
  const agent = this._framework.createSessionAgent(`feishu_${openId}`, {
263
263
  systemPrompt: this.systemPrompt,
264
264
  sharedPrompt: `工作目录: {{WORK_DIR}}`,
265
- metadata: { WORK_DIR: process.cwd() }
265
+ metadata: { WORK_DIR: this._framework?.getCwd?.() ?? process.cwd() }
266
266
  })
267
267
  this._sessionAgents.set(openId, agent)
268
268
 
@@ -26,7 +26,7 @@ class FileSystemPlugin extends Plugin {
26
26
  // 路径安全验证:防止路径穿越攻击
27
27
  const validatePath = (filePath, allowOutsideCwd = false) => {
28
28
  const resolved = path.resolve(filePath)
29
- const cwd = process.cwd()
29
+ const cwd = framework?.getCwd?.() ?? process.cwd()
30
30
 
31
31
  // 允许绝对路径且在允许列表中的路径(如果有的话)
32
32
  if (allowOutsideCwd) {
@@ -51,6 +51,7 @@ class FileSystemPlugin extends Plugin {
51
51
  recursive: z.boolean().optional().describe('是否递归')
52
52
  }),
53
53
  execute: async (args, framework) => {
54
+ const cwd = framework?.getCwd?.() ?? process.cwd()
54
55
  const dirPath = args.path || args.dirPath || '.'
55
56
  const recursive = args.recursive || false
56
57
  try {
@@ -60,7 +61,7 @@ class FileSystemPlugin extends Plugin {
60
61
  const entries = fs.readdirSync(currentPath, { withFileTypes: true })
61
62
  for (const entry of entries) {
62
63
  const fullPath = path.join(currentPath, entry.name)
63
- const relativePath = path.relative(process.cwd(), fullPath)
64
+ const relativePath = path.relative(cwd, fullPath)
64
65
  if (relativePath.includes('node_modules') || relativePath.includes('.git')) {
65
66
  continue
66
67
  }
@@ -476,7 +477,7 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
476
477
  execute: async (args, framework) => {
477
478
  const { path: filePath, edits } = args;
478
479
  const editDiff = require('../src/utils/edit-diff');
479
- const result = await editDiff.applyEditsToFile(filePath, edits, process.cwd());
480
+ const result = await editDiff.applyEditsToFile(filePath, edits, framework?.getCwd?.() ?? process.cwd());
480
481
  return result;
481
482
  }
482
483
  };
@@ -509,7 +510,7 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
509
510
  }),
510
511
  execute: async (args, framework) => {
511
512
  const pattern = args.pattern
512
- const dirPath = args.path || process.cwd()
513
+ const dirPath = args.path || (framework?.getCwd?.() ?? process.cwd())
513
514
  const targetFile = args.file
514
515
  const fileType = args.fileType
515
516
  const maxResults = args.maxResults || 100
@@ -625,7 +626,7 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
625
626
  if (results.length >= maxResults) break
626
627
 
627
628
  const fullPath = path.join(currentPath, entry.name)
628
- const relativePath = path.relative(process.cwd(), fullPath)
629
+ const relativePath = path.relative(cwd, fullPath)
629
630
 
630
631
  // 检查是否在排除目录中
631
632
  const shouldExclude = excludeDirs.some(exclude =>
@@ -696,7 +697,7 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
696
697
  }),
697
698
  execute: async (args, framework) => {
698
699
  const command = args.cmd || args.command || args.run
699
- const cwd = args.cwd || process.cwd()
700
+ const cwd = args.cwd || (framework?.getCwd?.() ?? process.cwd())
700
701
  const timeout = Math.min(args.timeout || 30000, 120000) // 最多 2 分钟
701
702
 
702
703
  // 验证命令:检查危险的 shell 模式
@@ -762,7 +763,7 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
762
763
  }),
763
764
  execute: async (args, framework) => {
764
765
  const command = args.cmd || args.command
765
- const cwd = args.cwd || process.cwd()
766
+ const cwd = args.cwd || (framework?.getCwd?.() ?? process.cwd())
766
767
  const timeout = Math.min(args.timeout || 30000, 120000)
767
768
 
768
769
  if (!command) {
@@ -30,6 +30,8 @@ class InstallPlugin extends Plugin {
30
30
 
31
31
  start(framework) {
32
32
  // 确保 node_modules 目录存在
33
+ const cwd = this._framework?.getCwd?.() ?? process.cwd()
34
+ this._agentDir = path.resolve(cwd, this._agentDir)
33
35
  this._nodeModulesDir = path.join(this._agentDir, 'node_modules')
34
36
  if (!fs.existsSync(this._nodeModulesDir)) {
35
37
  fs.mkdirSync(this._nodeModulesDir, { recursive: true })
@@ -46,10 +48,11 @@ class InstallPlugin extends Plugin {
46
48
  }),
47
49
  execute: async (args) => {
48
50
  const { package: packageName, path: targetPath, file: packageJsonPath } = args
51
+ const cwd = this._framework?.getCwd?.() ?? process.cwd()
49
52
 
50
53
  // 优先处理 file 参数(从 package.json 安装)
51
54
  if (packageJsonPath) {
52
- const resolvedPath = path.resolve(process.cwd(), packageJsonPath)
55
+ const resolvedPath = path.resolve(cwd, packageJsonPath)
53
56
  return this._installFromPackageJson(resolvedPath, targetPath)
54
57
  }
55
58
 
@@ -60,7 +63,7 @@ class InstallPlugin extends Plugin {
60
63
 
61
64
  // 只有 path,安装该目录的 package.json
62
65
  if (targetPath) {
63
- const resolvedPath = path.resolve(process.cwd(), targetPath)
66
+ const resolvedPath = path.resolve(cwd, targetPath)
64
67
  const pkgJson = path.join(resolvedPath, 'package.json')
65
68
  if (fs.existsSync(pkgJson)) {
66
69
  return this._installFromPackageJson(pkgJson, targetPath)
@@ -68,7 +71,7 @@ class InstallPlugin extends Plugin {
68
71
  return { success: false, error: `package.json not found at ${pkgJson}` }
69
72
  }
70
73
 
71
- // 什么都不提供,报错
74
+ // 都不提供,报错
72
75
  return { success: false, error: 'Must provide package name or package.json path' }
73
76
  }
74
77
  })
@@ -83,8 +86,9 @@ class InstallPlugin extends Plugin {
83
86
  */
84
87
  _installPackage(packageName, targetPath = null) {
85
88
  try {
89
+ const cwd = this._framework?.getCwd?.() ?? process.cwd()
86
90
  const installPath = targetPath
87
- ? path.resolve(process.cwd(), targetPath)
91
+ ? path.resolve(cwd, targetPath)
88
92
  : this._agentDir
89
93
 
90
94
  //log.info(` Installing ${packageName} to ${installPath}...`)
@@ -121,10 +125,11 @@ class InstallPlugin extends Plugin {
121
125
  */
122
126
  _installFromPackageJson(packageJsonPath, targetPath = null) {
123
127
  try {
124
- const resolvedPkgPath = path.resolve(process.cwd(), packageJsonPath)
128
+ const cwd = this._framework?.getCwd?.() ?? process.cwd()
129
+ const resolvedPkgPath = path.resolve(cwd, packageJsonPath)
125
130
  const pkgDir = path.dirname(resolvedPkgPath)
126
131
  const installPath = targetPath
127
- ? path.resolve(process.cwd(), targetPath)
132
+ ? path.resolve(cwd, targetPath)
128
133
  : pkgDir
129
134
 
130
135
  //log.info(` Installing dependencies from ${resolvedPkgPath} to ${installPath}...`)
@@ -210,9 +210,9 @@ function _extractJSON(text) {
210
210
  * MemoryStore - 双层存储(内存 + 文件)
211
211
  */
212
212
  class MemoryStore extends EventEmitter {
213
- constructor(baseDir) {
213
+ constructor(baseDir, cwd) {
214
214
  super()
215
- this._baseDir = path.resolve(process.cwd(), baseDir)
215
+ this._baseDir = path.resolve(cwd ?? process.cwd(), baseDir)
216
216
  this._memory = new Map() // memoryId -> memory object
217
217
  this._indexByType = new Map() // type -> Set of memoryIds
218
218
  this._indexByProject = new Map() // project -> Set of memoryIds
@@ -573,7 +573,7 @@ class MemoryPlugin extends Plugin {
573
573
 
574
574
  start(framework) {
575
575
  // 初始化存储
576
- this._store = new MemoryStore(this.config.memoryDir)
576
+ this._store = new MemoryStore(this.config.memoryDir, this._framework?.getCwd?.())
577
577
 
578
578
  // 创建复用的记忆提取子 Agent
579
579
  this._memoryAgent = framework.createSubAgent({
@@ -1276,7 +1276,7 @@ ${prompt}`
1276
1276
  reload(framework) {
1277
1277
  this._framework = framework
1278
1278
  // 重新初始化存储
1279
- this._store = new MemoryStore(this.config.memoryDir)
1279
+ this._store = new MemoryStore(this.config.memoryDir, this._framework?.getCwd?.())
1280
1280
  }
1281
1281
 
1282
1282
  uninstall(framework) {
@@ -212,8 +212,9 @@ class PluginManagerPlugin extends Plugin {
212
212
  * 发布插件到远程仓库
213
213
  */
214
214
  async _publishPlugin(pluginName, repo) {
215
- const pluginsDir = path.resolve(process.cwd(), '.foliko', 'plugins');
216
- const localPluginsDir = path.resolve(process.cwd(), 'plugins');
215
+ const cwd = this._framework?.getCwd?.() ?? process.cwd();
216
+ const pluginsDir = path.resolve(cwd, '.foliko', 'plugins');
217
+ const localPluginsDir = path.resolve(cwd, 'plugins');
217
218
 
218
219
  // 确定插件目录
219
220
  let actualPluginsDir = pluginsDir;
@@ -251,7 +252,7 @@ class PluginManagerPlugin extends Plugin {
251
252
 
252
253
  let isNewRepo = false;
253
254
  try {
254
- gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, process.cwd());
255
+ gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, this._framework?.getCwd?.() ?? process.cwd());
255
256
  } catch (err) {
256
257
  //log.info('Initializing new repository...');
257
258
  fs.mkdirSync(tmpDir, { recursive: true });
@@ -331,7 +332,8 @@ class PluginManagerPlugin extends Plugin {
331
332
  * 从远程仓库安装插件
332
333
  */
333
334
  async _installPlugin(pluginName, repo) {
334
- const localPluginsDir = path.resolve(process.cwd(), '.foliko', 'plugins');
335
+ const cwd = this._framework?.getCwd?.() ?? process.cwd();
336
+ const localPluginsDir = path.resolve(cwd, '.foliko', 'plugins');
335
337
  const tmpDir = path.join(require('os').tmpdir(), `foliko-plugin-install-${Date.now()}`);
336
338
 
337
339
  try {
@@ -339,7 +341,7 @@ class PluginManagerPlugin extends Plugin {
339
341
 
340
342
  // 克隆仓库
341
343
  fs.mkdirSync(tmpDir, { recursive: true });
342
- gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, process.cwd());
344
+ gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, cwd);
343
345
 
344
346
  // 查找插件
345
347
  const pluginDir = path.join(tmpDir, pluginName);
@@ -49,11 +49,7 @@ class QQPlugin extends Plugin {
49
49
  this.agent = null
50
50
  this.sessionId = null
51
51
 
52
- const saveDir = path.join(process.cwd(), '.foliko', 'data', this.name)
53
- this.downloader = new FileDownloader({
54
- retries: 3,
55
- baseDir: saveDir
56
- })
52
+ this.downloader = null
57
53
 
58
54
  this.systemPrompt = `你是一个 QQ 助手。
59
55
 
@@ -70,6 +66,13 @@ class QQPlugin extends Plugin {
70
66
 
71
67
  install(framework) {
72
68
  this._framework = framework
69
+ if (!this.downloader) {
70
+ const saveDir = path.join(this._framework?.getCwd?.() ?? process.cwd(), '.foliko', 'data', this.name)
71
+ this.downloader = new FileDownloader({
72
+ retries: 3,
73
+ baseDir: saveDir
74
+ })
75
+ }
73
76
  this._registerTools()
74
77
  return this
75
78
  }
@@ -527,7 +530,7 @@ class QQPlugin extends Plugin {
527
530
  name: `qq_${identifier}`,
528
531
  systemPrompt: this.systemPrompt,
529
532
  sharedPrompt: `工作目录: {{WORK_DIR}}`,
530
- metadata: { WORK_DIR: process.cwd(), platform: 'qq', identifier }
533
+ metadata: { WORK_DIR: this._framework?.getCwd?.() ?? process.cwd(), platform: 'qq', identifier }
531
534
  })
532
535
 
533
536
  this._sessionAgents.set(identifier, agent)
@@ -168,7 +168,7 @@ class RulesPlugin extends Plugin {
168
168
  * 从目录加载规则文件
169
169
  */
170
170
  loadFromDirectory(rulesDir) {
171
- const resolvedDir = path.resolve(process.cwd(), rulesDir)
171
+ const resolvedDir = path.resolve(this._framework?.getCwd?.() ?? process.cwd(), rulesDir)
172
172
 
173
173
  if (!fs.existsSync(resolvedDir)) {
174
174
  //log.info(` Rules directory not found: ${resolvedDir}`)