foliko 2.0.4 → 2.0.5
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/.editorconfig +56 -56
- package/.lintstagedrc +7 -7
- package/.prettierignore +29 -29
- package/.prettierrc +11 -11
- package/Dockerfile +63 -63
- package/docs/public-api.md +924 -6
- package/install.ps1 +129 -129
- package/install.sh +121 -121
- package/package.json +1 -1
- package/plugins/core/default/bootstrap.js +21 -0
- package/plugins/core/skill-manager/index.js +158 -0
- package/plugins/core/workflow/index.js +24 -0
- package/skills/find-skills/SKILL.md +133 -133
- package/src/agent/prompt-registry.js +95 -89
- package/src/framework/framework.js +616 -2
- package/src/plugin/base.js +5 -5
- package/tests/core/plugin-prompts.test.js +13 -13
- package/tests/core/prompt-registry.test.js +59 -51
|
@@ -3,40 +3,29 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* PromptRegistry — 全局系统提示词注册表
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - 可观测:list / preview / getPart / getSlots
|
|
11
|
-
*
|
|
12
|
-
* 与旧 API 的关系:
|
|
13
|
-
* - 完全独立的新 API,不破坏 MainAgent._systemPromptBuilder
|
|
14
|
-
* - 旧 registerPromptPart 调用继续工作(写入各 agent 自己的 builder)
|
|
15
|
-
* - 新代码推荐使用 framework.prompts.register(owner, name, provider, { slot })
|
|
16
|
-
*
|
|
17
|
-
* 渐进迁移路径:
|
|
18
|
-
* - P0:注册表可独立使用(plugins 主动注册,外部可预览)
|
|
19
|
-
* - P2:Plugin 基类支持声明式 prompts 字段
|
|
20
|
-
* - P5:MainAgent 完全切换到本注册表
|
|
6
|
+
* 简化设计:
|
|
7
|
+
* - priority: 数字越小越靠前(默认 100)
|
|
8
|
+
* - scope: 'global'(公用)| 'main'(主 agent)| agentId(特定子 agent)
|
|
9
|
+
* - 去掉槽位概念
|
|
21
10
|
*/
|
|
22
11
|
|
|
23
12
|
const { EventEmitter } = require('../common/events');
|
|
24
|
-
const
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
25
15
|
|
|
26
16
|
/**
|
|
27
17
|
* 单个 prompt part
|
|
28
|
-
* - provider 是 () => string|null,返回 null/空串时被 build 跳过
|
|
29
|
-
* - 内部带缓存,失效后才重新计算
|
|
30
18
|
*/
|
|
31
19
|
class PromptPart {
|
|
32
20
|
constructor(owner, name, provider, options = {}) {
|
|
33
21
|
this.owner = owner;
|
|
34
22
|
this.name = name;
|
|
35
23
|
this.provider = provider;
|
|
36
|
-
|
|
37
|
-
this.
|
|
24
|
+
// scope: 'global' | 'main' | string (agentId)
|
|
25
|
+
this.scope = options.scope || 'global';
|
|
26
|
+
// priority: 数字越小越靠前,默认 100
|
|
27
|
+
this.priority = options.priority ?? 100;
|
|
38
28
|
this.description = options.description || '';
|
|
39
|
-
this.metadata = options.metadata || {};
|
|
40
29
|
this._cached = null;
|
|
41
30
|
this._dirty = true;
|
|
42
31
|
this._error = null;
|
|
@@ -53,8 +42,6 @@ class PromptPart {
|
|
|
53
42
|
this._error = e;
|
|
54
43
|
this._cached = null;
|
|
55
44
|
this._dirty = false;
|
|
56
|
-
// 警告但不抛错 — 避免单个 part 失败导致整个 prompt 渲染失败
|
|
57
|
-
// eslint-disable-next-line no-console
|
|
58
45
|
console.warn(`[PromptRegistry:${this.owner}::${this.name}] provider failed:`, e.message);
|
|
59
46
|
}
|
|
60
47
|
}
|
|
@@ -70,61 +57,25 @@ class PromptPart {
|
|
|
70
57
|
* PromptRegistry 主类
|
|
71
58
|
*/
|
|
72
59
|
class PromptRegistry extends EventEmitter {
|
|
73
|
-
constructor(
|
|
60
|
+
constructor() {
|
|
74
61
|
super();
|
|
75
|
-
|
|
76
62
|
// key = "owner::name"
|
|
77
63
|
this._parts = new Map();
|
|
78
|
-
|
|
79
|
-
// slot 名称 → { order, label }
|
|
80
|
-
this._slots = new Map();
|
|
81
|
-
|
|
82
|
-
// 注入 slots:优先用参数,否则用全局 PROMPT_SLOT
|
|
83
|
-
const slots = options.slots || PROMPT_SLOT;
|
|
84
|
-
for (const [name, def] of Object.entries(slots)) {
|
|
85
|
-
if (!def || typeof def.order !== 'number') continue;
|
|
86
|
-
this._slots.set(name, {
|
|
87
|
-
order: def.order,
|
|
88
|
-
label: def.label || name,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 用于 render 时检测循环依赖
|
|
64
|
+
// 用于 build 时检测循环依赖
|
|
93
65
|
this._building = false;
|
|
94
66
|
}
|
|
95
67
|
|
|
96
|
-
// ==================== 槽位管理 ====================
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 定义/覆盖一个槽位
|
|
100
|
-
*/
|
|
101
|
-
defineSlot(name, order, label) {
|
|
102
|
-
if (typeof order !== 'number') throw new Error('slot order must be a number');
|
|
103
|
-
this._slots.set(name, { order, label: label || name });
|
|
104
|
-
this.emit('slot-defined', { name, order, label });
|
|
105
|
-
return this;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 获取所有槽位定义
|
|
110
|
-
*/
|
|
111
|
-
getSlots() {
|
|
112
|
-
return new Map(this._slots);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
68
|
// ==================== 注册/注销 ====================
|
|
116
69
|
|
|
117
70
|
/**
|
|
118
71
|
* 注册一个 prompt part
|
|
119
|
-
* @param {string} owner
|
|
72
|
+
* @param {string} owner 拥有者标识
|
|
120
73
|
* @param {string} name part 名(在 owner 内唯一)
|
|
121
74
|
* @param {Function} provider () => string|null
|
|
122
75
|
* @param {Object} [options]
|
|
123
|
-
* @param {string} [options.
|
|
124
|
-
* @param {number} [options.
|
|
125
|
-
* @param {string} [options.description]
|
|
126
|
-
* @param {Object} [options.metadata] 额外元数据
|
|
127
|
-
* @returns {PromptPart}
|
|
76
|
+
* @param {string} [options.scope='global'] 作用域: 'global'|'main'|agentId
|
|
77
|
+
* @param {number} [options.priority=100] 优先级(越小越靠前)
|
|
78
|
+
* @param {string} [options.description] 描述
|
|
128
79
|
*/
|
|
129
80
|
register(owner, name, provider, options = {}) {
|
|
130
81
|
if (!owner || typeof owner !== 'string') {
|
|
@@ -140,13 +91,53 @@ class PromptRegistry extends EventEmitter {
|
|
|
140
91
|
const key = `${owner}::${name}`;
|
|
141
92
|
const part = new PromptPart(owner, name, provider, options);
|
|
142
93
|
this._parts.set(key, part);
|
|
143
|
-
this.emit('register', { owner, name,
|
|
94
|
+
this.emit('register', { owner, name, scope: part.scope, priority: part.priority });
|
|
144
95
|
return part;
|
|
145
96
|
}
|
|
146
97
|
|
|
98
|
+
/**
|
|
99
|
+
* 注册一个 prompt part(从文件读取,支持变量插值)
|
|
100
|
+
* @param {string} owner 拥有者标识
|
|
101
|
+
* @param {string} filePath 文件路径
|
|
102
|
+
* @param {Object} [vars] 变量对象,模板中用 {{varName}} 引用
|
|
103
|
+
* @param {Object} [options] 同 register 选项
|
|
104
|
+
*/
|
|
105
|
+
registerFile(owner, filePath, vars = {}, options = {}) {
|
|
106
|
+
if (!owner || typeof owner !== 'string') {
|
|
107
|
+
throw new Error('PromptRegistry.registerFile: owner is required');
|
|
108
|
+
}
|
|
109
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
110
|
+
throw new Error('PromptRegistry.registerFile: filePath is required');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 读取文件内容
|
|
114
|
+
const fullPath = path.resolve(filePath);
|
|
115
|
+
if (!fs.existsSync(fullPath)) {
|
|
116
|
+
throw new Error(`PromptRegistry.registerFile: file not found: ${fullPath}`);
|
|
117
|
+
}
|
|
118
|
+
let content = fs.readFileSync(fullPath, 'utf-8');
|
|
119
|
+
|
|
120
|
+
// 变量插值
|
|
121
|
+
if (vars && typeof vars === 'object') {
|
|
122
|
+
content = content.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
|
123
|
+
if (Object.prototype.hasOwnProperty.call(vars, varName)) {
|
|
124
|
+
return String(vars[varName]);
|
|
125
|
+
}
|
|
126
|
+
return match; // 未找到的变量保留原样
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 转为 provider
|
|
131
|
+
const provider = () => content;
|
|
132
|
+
|
|
133
|
+
// 从文件名生成 name
|
|
134
|
+
const name = options.name || path.basename(filePath, path.extname(filePath));
|
|
135
|
+
|
|
136
|
+
return this.register(owner, name, provider, options);
|
|
137
|
+
}
|
|
138
|
+
|
|
147
139
|
/**
|
|
148
140
|
* 注销一个 part
|
|
149
|
-
* @returns {boolean} 是否真的删除了
|
|
150
141
|
*/
|
|
151
142
|
unregister(owner, name) {
|
|
152
143
|
const key = `${owner}::${name}`;
|
|
@@ -157,7 +148,6 @@ class PromptRegistry extends EventEmitter {
|
|
|
157
148
|
|
|
158
149
|
/**
|
|
159
150
|
* 清空指定 owner 的所有 parts
|
|
160
|
-
* @returns {number} 删除的 part 数
|
|
161
151
|
*/
|
|
162
152
|
clearOwner(owner) {
|
|
163
153
|
const prefix = `${owner}::`;
|
|
@@ -173,7 +163,7 @@ class PromptRegistry extends EventEmitter {
|
|
|
173
163
|
}
|
|
174
164
|
|
|
175
165
|
/**
|
|
176
|
-
* 失效指定 part
|
|
166
|
+
* 失效指定 part
|
|
177
167
|
*/
|
|
178
168
|
invalidate(owner, name) {
|
|
179
169
|
const part = this._parts.get(`${owner}::${name}`);
|
|
@@ -210,10 +200,14 @@ class PromptRegistry extends EventEmitter {
|
|
|
210
200
|
}
|
|
211
201
|
|
|
212
202
|
/**
|
|
213
|
-
* 列出所有 parts(按
|
|
203
|
+
* 列出所有 parts(按 priority 排序)
|
|
214
204
|
*/
|
|
215
205
|
list() {
|
|
216
|
-
return Array.from(this._parts.values()).sort((a, b) =>
|
|
206
|
+
return Array.from(this._parts.values()).sort((a, b) => {
|
|
207
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
208
|
+
if (a.owner !== b.owner) return a.owner.localeCompare(b.owner);
|
|
209
|
+
return a.name.localeCompare(b.name);
|
|
210
|
+
});
|
|
217
211
|
}
|
|
218
212
|
|
|
219
213
|
/**
|
|
@@ -224,19 +218,24 @@ class PromptRegistry extends EventEmitter {
|
|
|
224
218
|
}
|
|
225
219
|
|
|
226
220
|
/**
|
|
227
|
-
* 按
|
|
221
|
+
* 按 scope 过滤
|
|
228
222
|
*/
|
|
229
|
-
|
|
230
|
-
return this.list().filter((p) => p.
|
|
223
|
+
listByScope(scope) {
|
|
224
|
+
return this.list().filter((p) => p.scope === scope);
|
|
231
225
|
}
|
|
232
226
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return
|
|
227
|
+
/**
|
|
228
|
+
* 获取适用于指定 agent 的 parts
|
|
229
|
+
* @param {string|null} agentId agent ID,null 表示主 agent
|
|
230
|
+
*/
|
|
231
|
+
listForAgent(agentId = null) {
|
|
232
|
+
const isMain = agentId === null || agentId === 'main';
|
|
233
|
+
return this.list().filter((p) => {
|
|
234
|
+
if (p.scope === 'global') return true;
|
|
235
|
+
if (p.scope === 'main' && isMain) return true;
|
|
236
|
+
if (p.scope === agentId) return true;
|
|
237
|
+
return false;
|
|
238
|
+
});
|
|
240
239
|
}
|
|
241
240
|
|
|
242
241
|
count() {
|
|
@@ -246,16 +245,24 @@ class PromptRegistry extends EventEmitter {
|
|
|
246
245
|
// ==================== 渲染 ====================
|
|
247
246
|
|
|
248
247
|
/**
|
|
249
|
-
* 渲染完整 prompt
|
|
248
|
+
* 渲染完整 prompt(按 priority 拼接)
|
|
250
249
|
*/
|
|
251
250
|
build() {
|
|
251
|
+
return this.buildForAgent(null);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 渲染适用于指定 agent 的 prompt
|
|
256
|
+
* @param {string|null} agentId agent ID
|
|
257
|
+
*/
|
|
258
|
+
buildForAgent(agentId = null) {
|
|
252
259
|
if (this._building) {
|
|
253
260
|
throw new Error('PromptRegistry.build() re-entered — check provider for circular calls');
|
|
254
261
|
}
|
|
255
262
|
this._building = true;
|
|
256
263
|
try {
|
|
257
264
|
const sections = [];
|
|
258
|
-
for (const part of this.
|
|
265
|
+
for (const part of this.listForAgent(agentId)) {
|
|
259
266
|
const content = part.get();
|
|
260
267
|
if (content && content.trim()) sections.push(content.trim());
|
|
261
268
|
}
|
|
@@ -266,15 +273,14 @@ class PromptRegistry extends EventEmitter {
|
|
|
266
273
|
}
|
|
267
274
|
|
|
268
275
|
/**
|
|
269
|
-
* build()
|
|
276
|
+
* build() 的别名
|
|
270
277
|
*/
|
|
271
278
|
preview() {
|
|
272
279
|
return this.build();
|
|
273
280
|
}
|
|
274
281
|
|
|
275
282
|
/**
|
|
276
|
-
*
|
|
277
|
-
* 返回 [{ owner, name, slot, order, length, contentPreview }]
|
|
283
|
+
* 渲染结构化清单(调试用)
|
|
278
284
|
*/
|
|
279
285
|
inspect() {
|
|
280
286
|
return this.list().map((part) => {
|
|
@@ -282,8 +288,8 @@ class PromptRegistry extends EventEmitter {
|
|
|
282
288
|
return {
|
|
283
289
|
owner: part.owner,
|
|
284
290
|
name: part.name,
|
|
285
|
-
|
|
286
|
-
|
|
291
|
+
scope: part.scope,
|
|
292
|
+
priority: part.priority,
|
|
287
293
|
description: part.description,
|
|
288
294
|
hasContent: Boolean(content && content.trim()),
|
|
289
295
|
length: content ? content.length : 0,
|
|
@@ -293,4 +299,4 @@ class PromptRegistry extends EventEmitter {
|
|
|
293
299
|
}
|
|
294
300
|
}
|
|
295
301
|
|
|
296
|
-
module.exports = { PromptRegistry, PromptPart };
|
|
302
|
+
module.exports = { PromptRegistry, PromptPart };
|