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 +56 -56
- package/package.json +2 -2
- package/plugins/default-plugins.js +131 -19
- package/plugins/feishu-plugin.js +1 -1
- package/plugins/file-system-plugin.js +8 -7
- package/plugins/install-plugin.js +11 -6
- package/plugins/memory-plugin.js +4 -4
- package/plugins/plugin-manager-plugin.js +7 -5
- package/plugins/qq-plugin.js +9 -6
- package/plugins/rules-plugin.js +1 -1
- package/plugins/session-plugin.js +32 -23
- package/plugins/shell-executor-plugin.js +4 -1
- package/plugins/storage-plugin.js +1 -1
- package/plugins/subagent-plugin.js +1 -1
- package/plugins/telegram-plugin.js +2 -2
- package/plugins/weixin-plugin.js +9 -6
- package/skills/find-skills/SKILL.md +133 -133
- package/src/capabilities/skill-manager.js +3 -2
- package/src/capabilities/workflow-engine.js +2 -1
- package/src/core/agent-chat.js +4 -4
- package/src/core/agent.js +1 -1
- package/src/core/constants.js +4 -0
- package/src/core/framework.js +241 -2
- package/src/core/plugin-base.js +11 -0
- package/src/core/plugin-manager.js +18 -2
- package/website_v2/styles/animations.css +7 -7
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.
|
|
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": "
|
|
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
|
|
28
|
-
const
|
|
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
|
-
|
|
151
|
-
|
|
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(
|
|
518
|
-
{ dir: path.resolve(
|
|
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) {
|
package/plugins/feishu-plugin.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
132
|
+
? path.resolve(cwd, targetPath)
|
|
128
133
|
: pkgDir
|
|
129
134
|
|
|
130
135
|
//log.info(` Installing dependencies from ${resolvedPkgPath} to ${installPath}...`)
|
package/plugins/memory-plugin.js
CHANGED
|
@@ -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
|
|
216
|
-
const
|
|
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
|
|
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`,
|
|
344
|
+
gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, cwd);
|
|
343
345
|
|
|
344
346
|
// 查找插件
|
|
345
347
|
const pluginDir = path.join(tmpDir, pluginName);
|
package/plugins/qq-plugin.js
CHANGED
|
@@ -49,11 +49,7 @@ class QQPlugin extends Plugin {
|
|
|
49
49
|
this.agent = null
|
|
50
50
|
this.sessionId = null
|
|
51
51
|
|
|
52
|
-
|
|
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)
|
package/plugins/rules-plugin.js
CHANGED
|
@@ -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}`)
|