foliko 2.0.1 → 2.0.3
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/README.md +6 -6
- package/docs/public-api.md +91 -0
- package/docs/system-prompt.md +219 -0
- package/docs/usage.md +6 -6
- package/package.json +1 -1
- package/plugins/ambient/ExplorerLoop.js +1 -0
- package/plugins/ambient/index.js +5 -0
- package/plugins/core/coordinator/index.js +11 -6
- package/plugins/core/default/bootstrap.js +21 -3
- package/plugins/core/default/config.js +12 -3
- package/plugins/core/python-loader/index.js +3 -3
- package/plugins/core/scheduler/index.js +26 -2
- package/plugins/core/skill-manager/index.js +198 -151
- package/plugins/core/sub-agent/index.js +34 -15
- package/plugins/core/think/index.js +1 -0
- package/plugins/core/workflow/index.js +5 -4
- package/plugins/executors/data-splitter/index.js +14 -1
- package/plugins/executors/extension/index.js +51 -37
- package/plugins/executors/python/index.js +3 -3
- package/plugins/executors/shell/index.js +6 -4
- package/plugins/io/web/index.js +2 -1
- package/plugins/memory/index.js +29 -74
- package/plugins/messaging/email/handlers.js +1 -19
- package/plugins/messaging/email/monitor.js +2 -17
- package/plugins/messaging/email/reply.js +2 -1
- package/plugins/messaging/email/utils.js +20 -1
- package/plugins/messaging/feishu/index.js +1 -1
- package/plugins/messaging/qq/index.js +1 -1
- package/plugins/messaging/telegram/index.js +5 -1
- package/plugins/messaging/weixin/index.js +1 -1
- package/plugins/plugin-manager/index.js +1 -33
- package/plugins/tools/index.js +14 -1
- package/skills/plugins/SKILL.md +316 -0
- package/skills/skill-guide/SKILL.md +5 -5
- package/skills/{subagent-guide → subagents}/SKILL.md +1 -1
- package/skills/{workflow-guide → workflows}/SKILL.md +3 -3
- package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/DEBUGGING.md +197 -197
- package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/SKILL.md +391 -391
- package/src/agent/base.js +5 -20
- package/src/agent/index.js +20 -8
- package/src/agent/main.js +100 -23
- package/src/agent/prompt-registry.js +296 -0
- package/src/agent/sub-config.js +1 -78
- package/src/agent/sub.js +19 -24
- package/src/cli/commands/plugin.js +1 -60
- package/src/cli/ui/chat-ui-old.js +1 -1
- package/src/cli/ui/chat-ui.js +1 -1
- package/src/cli/ui/components/agent-mention-provider.js +1 -1
- package/src/common/constants.js +42 -0
- package/src/common/id.js +13 -0
- package/src/common/queue.js +2 -2
- package/src/context/compressor.js +17 -9
- package/src/framework/framework.js +185 -0
- package/src/framework/index.js +1 -2
- package/src/framework/lifecycle.js +102 -1
- package/src/framework/loader.js +1 -55
- package/src/index.js +11 -2
- package/src/plugin/base.js +69 -55
- package/src/plugin/loader.js +1 -57
- package/src/plugin/manager.js +13 -4
- package/src/session/entry.js +1 -11
- package/src/storage/manager.js +1 -12
- package/src/tool/executor.js +2 -1
- package/src/tool/router.js +5 -5
- package/src/utils/data-splitter.js +2 -1
- package/src/utils/index.js +150 -0
- package/src/utils/message-validator.js +21 -17
- package/src/utils/plugin-helpers.js +19 -5
- package/subagent.md +2 -2
- package/tests/core/plugin-prompts.test.js +219 -0
- package/tests/core/prompt-registry.test.js +209 -0
- package/src/cli/utils/plugin-config.js +0 -50
- package/src/config/plugin-config.js +0 -50
- /package/skills/{ambient-agent → ambient}/SKILL.md +0 -0
- /package/skills/{foliko-dev → foliko}/AGENTS.md +0 -0
- /package/skills/{foliko-dev → foliko}/SKILL.md +0 -0
- /package/skills/{mcp-usage → mcp}/SKILL.md +0 -0
- /package/skills/{plugin-guide → plugins-guide}/SKILL.md +0 -0
- /package/skills/{python-plugin-dev → python}/SKILL.md +0 -0
package/src/framework/loader.js
CHANGED
|
@@ -6,60 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Load agent config from .foliko/ directory
|
|
13
|
-
* @param {Framework} framework
|
|
14
|
-
* @param {string} agentDir
|
|
15
|
-
* @returns {Object}
|
|
16
|
-
*/
|
|
17
|
-
function loadAgentConfig(framework, agentDir = '.foliko') {
|
|
18
|
-
const cwd = framework.getCwd ? framework.getCwd() : process.cwd();
|
|
19
|
-
const folikoDir = path.resolve(cwd, agentDir);
|
|
20
|
-
const config = {
|
|
21
|
-
plugins: [],
|
|
22
|
-
skills: [],
|
|
23
|
-
ai: {},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Load plugins.json
|
|
27
|
-
const pluginsPath = path.join(folikoDir, 'plugins.json');
|
|
28
|
-
if (fs.existsSync(pluginsPath)) {
|
|
29
|
-
try {
|
|
30
|
-
const pluginsConfig = JSON.parse(fs.readFileSync(pluginsPath, 'utf-8'));
|
|
31
|
-
if (Array.isArray(pluginsConfig)) {
|
|
32
|
-
config.plugins = pluginsConfig;
|
|
33
|
-
} else if (pluginsConfig.plugins) {
|
|
34
|
-
config.plugins = pluginsConfig.plugins;
|
|
35
|
-
}
|
|
36
|
-
if (pluginsConfig.ai) config.ai = { ...config.ai, ...pluginsConfig.ai };
|
|
37
|
-
if (pluginsConfig.skillsDirs) config.skillsDirs = pluginsConfig.skillsDirs;
|
|
38
|
-
} catch (err) {
|
|
39
|
-
framework.logger?.warn?.('Failed to load plugins.json:', err.message);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Load agent configs from agents/ directory
|
|
44
|
-
const agentsDir = path.join(folikoDir, 'agents');
|
|
45
|
-
if (fs.existsSync(agentsDir)) {
|
|
46
|
-
try {
|
|
47
|
-
const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.json'));
|
|
48
|
-
config.agentFiles = files.map(f => path.join(agentsDir, f));
|
|
49
|
-
} catch { /* ignore */ }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Load skills config
|
|
53
|
-
const skillsDir = path.join(folikoDir, 'skills');
|
|
54
|
-
if (fs.existsSync(skillsDir)) {
|
|
55
|
-
try {
|
|
56
|
-
config.skillsDirs = config.skillsDirs || [];
|
|
57
|
-
config.skillsDirs.push(skillsDir);
|
|
58
|
-
} catch { /* ignore */ }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return config;
|
|
62
|
-
}
|
|
63
9
|
|
|
64
10
|
/**
|
|
65
11
|
* Load default plugins based on config
|
|
@@ -75,4 +21,4 @@ async function loadDefaultPlugins(framework, config = {}) {
|
|
|
75
21
|
return framework.pluginManager.getAll().map(p => p.name);
|
|
76
22
|
}
|
|
77
23
|
|
|
78
|
-
module.exports = {
|
|
24
|
+
module.exports = { loadDefaultPlugins };
|
package/src/index.js
CHANGED
|
@@ -15,12 +15,12 @@ require('dotenv').config({ override: true });
|
|
|
15
15
|
// Framework
|
|
16
16
|
// ============================================================
|
|
17
17
|
const { Framework } = require('./framework/framework');
|
|
18
|
-
const { bootstrap, ready, destroy } = require('./framework/lifecycle');
|
|
18
|
+
const { bootstrap, ready, destroy, setCwd, rescanProject, reloadAll } = require('./framework/lifecycle');
|
|
19
19
|
|
|
20
20
|
// ============================================================
|
|
21
21
|
// Agent
|
|
22
22
|
// ============================================================
|
|
23
|
-
const { createAgent, MainAgent, SubAgent, WorkerAgent } = require('./agent');
|
|
23
|
+
const { createAgent, MainAgent, SubAgent, WorkerAgent, PromptRegistry } = require('./agent');
|
|
24
24
|
|
|
25
25
|
// ============================================================
|
|
26
26
|
// Plugin
|
|
@@ -70,6 +70,9 @@ exports.createAgent = createAgent;
|
|
|
70
70
|
|
|
71
71
|
exports.ready = ready;
|
|
72
72
|
exports.destroy = destroy;
|
|
73
|
+
exports.setCwd = setCwd;
|
|
74
|
+
exports.rescanProject = rescanProject;
|
|
75
|
+
exports.reloadAll = reloadAll;
|
|
73
76
|
|
|
74
77
|
// --- 上下文管理 ---
|
|
75
78
|
|
|
@@ -113,6 +116,11 @@ exports.withPython = (config = {}) => {
|
|
|
113
116
|
exports.z = require('zod');
|
|
114
117
|
exports.LLM = llm;
|
|
115
118
|
|
|
119
|
+
// --- 常量 ---
|
|
120
|
+
exports.PROMPT_SLOT = require('./common/constants').PROMPT_SLOT;
|
|
121
|
+
exports.PROMPT_PRIORITY = require('./common/constants').PROMPT_PRIORITY;
|
|
122
|
+
exports.PROMPT_PRIORITY_TO_SLOT = require('./common/constants').PROMPT_PRIORITY_TO_SLOT;
|
|
123
|
+
|
|
116
124
|
// --- 框架核心类(用于扩展) ---
|
|
117
125
|
|
|
118
126
|
exports.Framework = Framework;
|
|
@@ -123,6 +131,7 @@ exports.ToolRegistry = ToolRegistry;
|
|
|
123
131
|
exports.ToolExecutor = ToolExecutor;
|
|
124
132
|
exports.Plugin = Plugin;
|
|
125
133
|
exports.PluginManager = PluginManager;
|
|
134
|
+
exports.PromptRegistry = PromptRegistry;
|
|
126
135
|
exports.EventEmitter = EventEmitter;
|
|
127
136
|
exports.Logger = Logger;
|
|
128
137
|
|
package/src/plugin/base.js
CHANGED
|
@@ -20,10 +20,30 @@ class Plugin {
|
|
|
20
20
|
|
|
21
21
|
_framework = null;
|
|
22
22
|
_subAgents = [];
|
|
23
|
-
_promptParts = [];
|
|
24
|
-
_deferredPromptParts = null;
|
|
25
23
|
tools = {};
|
|
26
24
|
|
|
25
|
+
/**
|
|
26
|
+
* 声明式 prompt parts(推荐使用,替代 start() 中的 registerPromptPart 调用)
|
|
27
|
+
*
|
|
28
|
+
* 用法:
|
|
29
|
+
* prompts = [
|
|
30
|
+
* {
|
|
31
|
+
* name: 'my-part',
|
|
32
|
+
* slot: 'BEHAVIOR', // 可选,PROMPT_SLOT 中的一个;默认 CUSTOM
|
|
33
|
+
* order: 50, // 可选,覆盖 slot 默认 order
|
|
34
|
+
* description: '描述', // 可选
|
|
35
|
+
* provider() { // 'this' 绑定到 plugin 实例;不要用箭头函数
|
|
36
|
+
* return this._buildSomething();
|
|
37
|
+
* },
|
|
38
|
+
* },
|
|
39
|
+
* ];
|
|
40
|
+
*
|
|
41
|
+
* 生命周期:
|
|
42
|
+
* - Plugin.start() 时自动注册到 framework.prompts
|
|
43
|
+
* - Plugin.uninstall() / Plugin.reload() 时自动清理(clearOwner(this.name))
|
|
44
|
+
*/
|
|
45
|
+
prompts = [];
|
|
46
|
+
|
|
27
47
|
install(framework) {
|
|
28
48
|
this._framework = framework;
|
|
29
49
|
return this;
|
|
@@ -56,9 +76,51 @@ class Plugin {
|
|
|
56
76
|
|
|
57
77
|
start(framework) {
|
|
58
78
|
this._autoRegisterSubAgents();
|
|
79
|
+
this._autoRegisterPrompts();
|
|
59
80
|
return this;
|
|
60
81
|
}
|
|
61
82
|
|
|
83
|
+
/**
|
|
84
|
+
* 自动注册 this.prompts 声明的所有 part 到 framework.prompts
|
|
85
|
+
* - 失败一个不影响其他
|
|
86
|
+
* - 'this' 在 provider 中绑定到 plugin 实例(必须用普通 function,箭头函数的 this 是词法的)
|
|
87
|
+
*/
|
|
88
|
+
_autoRegisterPrompts() {
|
|
89
|
+
const reg = this._framework?.prompts;
|
|
90
|
+
if (!reg) return;
|
|
91
|
+
if (!Array.isArray(this.prompts) || this.prompts.length === 0) return;
|
|
92
|
+
|
|
93
|
+
for (const entry of this.prompts) {
|
|
94
|
+
if (!entry || !entry.name || typeof entry.provider !== 'function') {
|
|
95
|
+
log.warn(`[${this.name}] Invalid prompt entry: missing name or provider`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
// bind 让 provider 中的 this 指向 plugin 实例
|
|
100
|
+
const boundProvider = entry.provider.bind(this);
|
|
101
|
+
reg.register(this.name, entry.name, boundProvider, {
|
|
102
|
+
slot: entry.slot,
|
|
103
|
+
order: entry.order,
|
|
104
|
+
description: entry.description,
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
log.error(`[${this.name}] Failed to register prompt part "${entry.name}":`, err.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 清理当前 plugin 在 framework.prompts 中注册的所有 part
|
|
114
|
+
*/
|
|
115
|
+
_cleanupPrompts() {
|
|
116
|
+
const reg = this._framework?.prompts;
|
|
117
|
+
if (!reg) return;
|
|
118
|
+
const removed = reg.clearOwner(this.name);
|
|
119
|
+
if (removed > 0) {
|
|
120
|
+
log.debug(`[${this.name}] cleared ${removed} prompt part(s)`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
62
124
|
_autoRegisterSubAgents() {
|
|
63
125
|
const agentsToRegister = this._deferredSubAgents || this.agents;
|
|
64
126
|
if (!agentsToRegister || !Array.isArray(agentsToRegister) || agentsToRegister.length === 0) {
|
|
@@ -139,15 +201,10 @@ class Plugin {
|
|
|
139
201
|
reload(framework) {
|
|
140
202
|
this._framework = framework;
|
|
141
203
|
this._deferredSubAgents = null;
|
|
142
|
-
this._deferredPromptParts = null;
|
|
143
204
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
} catch (err) {
|
|
148
|
-
log.error(` Failed to re-register prompt part ${part.name}:`, err.message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
205
|
+
// 先清理旧的,再重新注册 declarative prompts
|
|
206
|
+
this._cleanupPrompts();
|
|
207
|
+
this._autoRegisterPrompts();
|
|
151
208
|
|
|
152
209
|
this._autoRegisterSubAgents();
|
|
153
210
|
}
|
|
@@ -157,15 +214,8 @@ class Plugin {
|
|
|
157
214
|
}
|
|
158
215
|
|
|
159
216
|
uninstall(framework) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
framework._mainAgent?.unregisterPromptPart(part.name);
|
|
163
|
-
} catch (err) {
|
|
164
|
-
// ignore
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
this._promptParts = [];
|
|
168
|
-
this._deferredPromptParts = null;
|
|
217
|
+
// 清理 framework.prompts 中该插件注册的所有 part
|
|
218
|
+
this._cleanupPrompts();
|
|
169
219
|
|
|
170
220
|
for (const plugin of this._subAgents) {
|
|
171
221
|
try {
|
|
@@ -178,42 +228,6 @@ class Plugin {
|
|
|
178
228
|
this._framework = null;
|
|
179
229
|
}
|
|
180
230
|
|
|
181
|
-
registerPromptPart(name, priority = 100, provider) {
|
|
182
|
-
if (!this._framework) return;
|
|
183
|
-
|
|
184
|
-
this._promptParts.push({ name, priority, provider });
|
|
185
|
-
|
|
186
|
-
const mainAgent = this._framework.getMainAgent();
|
|
187
|
-
if (mainAgent) {
|
|
188
|
-
mainAgent.registerPromptPart(name, priority, provider);
|
|
189
|
-
this._framework.syncPromptPartsToSubagents();
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!this._deferredPromptParts) {
|
|
194
|
-
this._deferredPromptParts = [];
|
|
195
|
-
}
|
|
196
|
-
this._deferredPromptParts.push({ name, priority, provider });
|
|
197
|
-
|
|
198
|
-
const alreadyListening = this._framework._events?.['framework:ready']?.length > 0;
|
|
199
|
-
this._framework.once('framework:ready', () => {
|
|
200
|
-
setTimeout(() => {
|
|
201
|
-
if (this._deferredPromptParts) {
|
|
202
|
-
for (const part of this._deferredPromptParts) {
|
|
203
|
-
try {
|
|
204
|
-
const mainAgent = this._framework.getMainAgent();
|
|
205
|
-
mainAgent?.registerPromptPart(part.name, part.priority, part.provider);
|
|
206
|
-
} catch (err) {
|
|
207
|
-
log.error(` Failed to register prompt part ${part.name}:`, err.message);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
this._deferredPromptParts = null;
|
|
211
|
-
this._framework.syncPromptPartsToSubagents();
|
|
212
|
-
}
|
|
213
|
-
}, 100);
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
231
|
getInfo() {
|
|
218
232
|
return {
|
|
219
233
|
name: this.name,
|
package/src/plugin/loader.js
CHANGED
|
@@ -5,66 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const
|
|
8
|
+
const { resolvePluginPath, scanPluginNames } = require('../utils/plugin-helpers');
|
|
9
9
|
const { logger } = require('../common/logger');
|
|
10
10
|
const log = logger.child('plugin-loader');
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Resolve plugin path from name
|
|
14
|
-
* @param {string} pluginName
|
|
15
|
-
* @param {Object} options
|
|
16
|
-
* @returns {string|null}
|
|
17
|
-
*/
|
|
18
|
-
function resolvePluginPath(pluginName, options = {}) {
|
|
19
|
-
const { pluginDirs = [], searchPaths = [] } = options;
|
|
20
|
-
|
|
21
|
-
// Check explicit paths first
|
|
22
|
-
const allPaths = [...pluginDirs, ...searchPaths];
|
|
23
|
-
for (const dir of allPaths) {
|
|
24
|
-
const fullPath = path.resolve(dir, pluginName);
|
|
25
|
-
if (fs.existsSync(fullPath)) {
|
|
26
|
-
return fullPath;
|
|
27
|
-
}
|
|
28
|
-
const indexJs = path.join(fullPath, 'index.js');
|
|
29
|
-
if (fs.existsSync(indexJs)) {
|
|
30
|
-
return fullPath;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Check node_modules
|
|
35
|
-
try {
|
|
36
|
-
const resolved = require.resolve(pluginName);
|
|
37
|
-
return resolved;
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Scan directory for plugin names
|
|
45
|
-
* @param {string} dir
|
|
46
|
-
* @returns {string[]}
|
|
47
|
-
*/
|
|
48
|
-
function scanPluginNames(dir) {
|
|
49
|
-
if (!fs.existsSync(dir)) return [];
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
return fs.readdirSync(dir)
|
|
53
|
-
.filter(f => {
|
|
54
|
-
const fullPath = path.join(dir, f);
|
|
55
|
-
const stat = fs.statSync(fullPath);
|
|
56
|
-
if (stat.isDirectory()) {
|
|
57
|
-
return fs.existsSync(path.join(fullPath, 'index.js'));
|
|
58
|
-
}
|
|
59
|
-
return f.endsWith('.js');
|
|
60
|
-
})
|
|
61
|
-
.map(f => f.replace(/\.js$/, ''));
|
|
62
|
-
} catch (err) {
|
|
63
|
-
log.warn(`Failed to scan plugin directory ${dir}: ${err.message}`);
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
12
|
/**
|
|
69
13
|
* Load a plugin from a file path
|
|
70
14
|
* @param {string} pluginPath
|
package/src/plugin/manager.js
CHANGED
|
@@ -335,12 +335,21 @@ class PluginManager {
|
|
|
335
335
|
this.framework?.emit('plugin:reloaded', newInstance);
|
|
336
336
|
}
|
|
337
337
|
} else {
|
|
338
|
-
// 没有 sourcePath
|
|
338
|
+
// 没有 sourcePath,用现有实例重启
|
|
339
339
|
if (typeof entry.instance.reload === 'function') {
|
|
340
|
+
// 有自定义 reload,由它处理完整重启(内部应调 start)
|
|
340
341
|
await entry.instance.reload(this.framework);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
} else {
|
|
343
|
+
// 没有 reload,走完整 uninstall + install + start
|
|
344
|
+
if (typeof entry.instance.uninstall === 'function') {
|
|
345
|
+
entry.instance.uninstall(this.framework);
|
|
346
|
+
}
|
|
347
|
+
if (typeof entry.instance.install === 'function') {
|
|
348
|
+
entry.instance.install(this.framework);
|
|
349
|
+
}
|
|
350
|
+
if (typeof entry.instance.start === 'function') {
|
|
351
|
+
await entry.instance.start(this.framework);
|
|
352
|
+
}
|
|
344
353
|
}
|
|
345
354
|
entry.instance._started = true;
|
|
346
355
|
}
|
package/src/session/entry.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { randomUUID } = require('crypto');
|
|
7
|
+
const { generateEntryId } = require('../common/id');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Entry types for session tree entries
|
|
@@ -21,17 +22,6 @@ const EntryTypes = {
|
|
|
21
22
|
LEAF: 'leaf'
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
* Generate a unique short ID (8 hex chars, collision-checked)
|
|
26
|
-
*/
|
|
27
|
-
function generateEntryId(byId) {
|
|
28
|
-
for (let i = 0; i < 100; i++) {
|
|
29
|
-
const id = randomUUID().slice(0, 8);
|
|
30
|
-
if (!byId.has(id)) return id;
|
|
31
|
-
}
|
|
32
|
-
return randomUUID();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
25
|
/**
|
|
36
26
|
* Generate UUID v7 for session IDs
|
|
37
27
|
*/
|
package/src/storage/manager.js
CHANGED
|
@@ -5,18 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Generate unique entry ID
|
|
12
|
-
*/
|
|
13
|
-
function generateEntryId(existingIds) {
|
|
14
|
-
for (let i = 0; i < 100; i++) {
|
|
15
|
-
const id = randomUUID().slice(0, 8);
|
|
16
|
-
if (!existingIds.has(id)) return id;
|
|
17
|
-
}
|
|
18
|
-
return randomUUID();
|
|
19
|
-
}
|
|
8
|
+
const { generateEntryId } = require('../common/id');
|
|
20
9
|
|
|
21
10
|
class StorageEntry {
|
|
22
11
|
constructor(key, value, timestamp) {
|
package/src/tool/executor.js
CHANGED
|
@@ -71,7 +71,8 @@ class ToolExecutor extends EventEmitter {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
validateToolCalls(messages) {
|
|
74
|
-
|
|
74
|
+
const registeredTools = new Set(this._tools.keys());
|
|
75
|
+
return validateToolCalls(messages, { availableTools: registeredTools });
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
getStats() {
|
package/src/tool/router.js
CHANGED
|
@@ -23,7 +23,7 @@ const INTENT_PATTERNS = {
|
|
|
23
23
|
'写代码', '开发', '编程', '创建插件', '编写函数',
|
|
24
24
|
'code', 'develop', 'plugin', '编程',
|
|
25
25
|
],
|
|
26
|
-
tools: ['
|
|
26
|
+
tools: ['skill_load', 'read_file', 'write_file', 'execute_command', 'shell_exec'],
|
|
27
27
|
},
|
|
28
28
|
system_info: {
|
|
29
29
|
keywords: [
|
|
@@ -34,19 +34,19 @@ const INTENT_PATTERNS = {
|
|
|
34
34
|
},
|
|
35
35
|
data_analysis: {
|
|
36
36
|
keywords: ['分析', '数据', '统计', '查询', 'analyze', 'data', 'statistics', 'query'],
|
|
37
|
-
tools: ['execute_command', '
|
|
37
|
+
tools: ['execute_command', 'py_execute', 'search_file'],
|
|
38
38
|
},
|
|
39
39
|
plugin_management: {
|
|
40
40
|
keywords: ['插件', '重载', '加载插件', 'plugin', 'reload', 'load plugin'],
|
|
41
|
-
tools: ['reload_plugins', 'list_plugins', '
|
|
41
|
+
tools: ['reload_plugins', 'list_plugins', 'skill_load'],
|
|
42
42
|
},
|
|
43
43
|
shell_command: {
|
|
44
44
|
keywords: ['命令', '终端', 'shell', '执行', 'command', 'terminal', 'bash', 'cmd'],
|
|
45
|
-
tools: ['
|
|
45
|
+
tools: ['shell_exec', 'powershell', 'execute_command'],
|
|
46
46
|
},
|
|
47
47
|
skill_usage: {
|
|
48
48
|
keywords: ['技能', 'skill', '使用技能', '加载技能'],
|
|
49
|
-
tools: ['
|
|
49
|
+
tools: ['skill_load'],
|
|
50
50
|
},
|
|
51
51
|
scheduling: {
|
|
52
52
|
keywords: [
|
|
@@ -168,13 +168,14 @@ class DataSplitter {
|
|
|
168
168
|
const processChunk = async (chunk) => {
|
|
169
169
|
const chunkIndex = chunk.index;
|
|
170
170
|
|
|
171
|
-
// 为当前块创建子 Agent
|
|
171
|
+
// 为当前块创建子 Agent(隐藏,不在指令系统中显示)
|
|
172
172
|
const subagent = this.framework.createSubAgent({
|
|
173
173
|
name: `${agentName}-chunk-${chunkIndex}`,
|
|
174
174
|
role: agentRole,
|
|
175
175
|
systemPrompt: `你是${agentRole},负责处理大数据中的第 ${chunkIndex + 1}/${totalChunks} 块。`,
|
|
176
176
|
maxRetries: this.maxRetries,
|
|
177
177
|
disableTools: true, // 分拆处理只做文本分析,不需要额外工具
|
|
178
|
+
hidden: true, // 隐藏子Agent,不在指令系统中显示
|
|
178
179
|
});
|
|
179
180
|
|
|
180
181
|
try {
|
package/src/utils/index.js
CHANGED
|
@@ -47,6 +47,80 @@ const {
|
|
|
47
47
|
combineBoundaries,
|
|
48
48
|
} = require('../common/errors');
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 解析 YAML frontmatter
|
|
52
|
+
* @param {string} content
|
|
53
|
+
* @returns {Object|null}
|
|
54
|
+
*/
|
|
55
|
+
function parseFrontmatter(content) {
|
|
56
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
57
|
+
if (!match) return null;
|
|
58
|
+
|
|
59
|
+
const frontmatter = {};
|
|
60
|
+
const lines = match[1].split('\n');
|
|
61
|
+
let currentKey = null;
|
|
62
|
+
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
// 处理 metadata 嵌套
|
|
65
|
+
if (currentKey === 'metadata') {
|
|
66
|
+
if (line.match(/^ {2}[a-zA-Z]/)) {
|
|
67
|
+
const colonIndex = line.indexOf(':');
|
|
68
|
+
if (colonIndex > 0) {
|
|
69
|
+
const key = line.substring(2, colonIndex).trim();
|
|
70
|
+
const value = line
|
|
71
|
+
.substring(colonIndex + 1)
|
|
72
|
+
.trim()
|
|
73
|
+
.replace(/^["']|["']$/g, '');
|
|
74
|
+
frontmatter[currentKey][key] = value;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
} else if (line.match(/^[a-zA-Z]/)) {
|
|
78
|
+
currentKey = null;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const colonIndex = line.indexOf(':');
|
|
84
|
+
if (colonIndex === -1) continue;
|
|
85
|
+
|
|
86
|
+
const key = line.substring(0, colonIndex).trim();
|
|
87
|
+
let value = line.substring(colonIndex + 1).trim();
|
|
88
|
+
|
|
89
|
+
// 移除引号
|
|
90
|
+
if (
|
|
91
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
92
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
93
|
+
) {
|
|
94
|
+
value = value.slice(1, -1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (key === 'metadata') {
|
|
98
|
+
currentKey = 'metadata';
|
|
99
|
+
frontmatter.metadata = {};
|
|
100
|
+
} else if (key === 'allowed-tools' || key === 'skills' || key === 'tools' || key === 'tags') {
|
|
101
|
+
// 去除首尾空白和方括号
|
|
102
|
+
value = value.trim().replace(/^\[|\]$/g, '');
|
|
103
|
+
frontmatter[key] = value
|
|
104
|
+
.split(',')
|
|
105
|
+
.map((v) => v.trim().replace(/^["']|["']$/g, ''))
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
} else {
|
|
108
|
+
frontmatter[key] = value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return frontmatter;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 移除 frontmatter,只保留正文内容
|
|
117
|
+
* @param {string} content
|
|
118
|
+
* @returns {string}
|
|
119
|
+
*/
|
|
120
|
+
function stripFrontmatter(content) {
|
|
121
|
+
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
|
|
122
|
+
}
|
|
123
|
+
|
|
50
124
|
/**
|
|
51
125
|
* 清理 LLM 回复中的思考标记
|
|
52
126
|
* @param {string} text
|
|
@@ -530,6 +604,8 @@ module.exports = {
|
|
|
530
604
|
// 工具函数
|
|
531
605
|
cleanResponse,
|
|
532
606
|
safeJsonParse,
|
|
607
|
+
parseFrontmatter,
|
|
608
|
+
stripFrontmatter,
|
|
533
609
|
deepClone,
|
|
534
610
|
debounce,
|
|
535
611
|
throttle,
|
|
@@ -539,4 +615,78 @@ module.exports = {
|
|
|
539
615
|
|
|
540
616
|
// Edit/Diff
|
|
541
617
|
editDiff: require('../common/diff'),
|
|
618
|
+
|
|
619
|
+
// 插件发布/安装相关
|
|
620
|
+
DEFAULT_REPO: 'https://github.com/chnak/foliko-plugins.git',
|
|
621
|
+
|
|
622
|
+
IGNORE_PATTERNS: [
|
|
623
|
+
'node_modules',
|
|
624
|
+
'.git',
|
|
625
|
+
'.env',
|
|
626
|
+
'.DS_Store',
|
|
627
|
+
'Thumbs.db',
|
|
628
|
+
'*.log',
|
|
629
|
+
'*.lock',
|
|
630
|
+
'*.bak',
|
|
631
|
+
'.claude',
|
|
632
|
+
'.foliko',
|
|
633
|
+
'examples',
|
|
634
|
+
'dist',
|
|
635
|
+
'build',
|
|
636
|
+
'coverage',
|
|
637
|
+
'tests',
|
|
638
|
+
'__tests__',
|
|
639
|
+
'*.test.js',
|
|
640
|
+
'*.spec.js',
|
|
641
|
+
'package-lock.json',
|
|
642
|
+
'yarn.lock',
|
|
643
|
+
'pnpm-lock.yaml',
|
|
644
|
+
],
|
|
645
|
+
|
|
646
|
+
shouldIgnore(name) {
|
|
647
|
+
return this.IGNORE_PATTERNS.some((pattern) => {
|
|
648
|
+
if (pattern.startsWith('*.')) {
|
|
649
|
+
return name.endsWith(pattern.slice(1));
|
|
650
|
+
}
|
|
651
|
+
return name === pattern;
|
|
652
|
+
});
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
copyDirRecursive(src, dest) {
|
|
656
|
+
const fs = require('fs');
|
|
657
|
+
if (!fs.existsSync(src)) return;
|
|
658
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
659
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
660
|
+
for (const entry of entries) {
|
|
661
|
+
const srcPath = path.join(src, entry.name);
|
|
662
|
+
const destPath = path.join(dest, entry.name);
|
|
663
|
+
if (this.shouldIgnore(entry.name)) continue;
|
|
664
|
+
if (entry.isDirectory()) {
|
|
665
|
+
this.copyDirRecursive(srcPath, destPath);
|
|
666
|
+
} else {
|
|
667
|
+
fs.copyFileSync(srcPath, destPath);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
parseGitUrl(url) {
|
|
673
|
+
const patterns = [
|
|
674
|
+
/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
675
|
+
/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
676
|
+
];
|
|
677
|
+
for (const pattern of patterns) {
|
|
678
|
+
const match = url.match(pattern);
|
|
679
|
+
if (match) return { owner: match[1], repo: match[2] };
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
gitCommand(args, cwd) {
|
|
685
|
+
const { execSync } = require('child_process');
|
|
686
|
+
try {
|
|
687
|
+
return execSync(`git ${args}`, { cwd, encoding: 'utf-8', stdio: 'pipe' });
|
|
688
|
+
} catch (err) {
|
|
689
|
+
return err.stdout || err.stderr || '';
|
|
690
|
+
}
|
|
691
|
+
},
|
|
542
692
|
};
|