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/agent/base.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* BaseAgent - 所有 Agent 类型的公共基类
|
|
5
|
-
* 提供事件、状态、prompt
|
|
5
|
+
* 提供事件、状态、prompt 等公共功能
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const { EventEmitter } = require('../common/events');
|
|
9
|
-
const {
|
|
9
|
+
const { PromptRegistry } = require('./prompt-registry');
|
|
10
10
|
|
|
11
11
|
class BaseAgent extends EventEmitter {
|
|
12
12
|
constructor(framework, config = {}) {
|
|
@@ -15,7 +15,9 @@ class BaseAgent extends EventEmitter {
|
|
|
15
15
|
this.config = config;
|
|
16
16
|
this.name = config.name || 'Agent';
|
|
17
17
|
this._status = 'idle';
|
|
18
|
-
|
|
18
|
+
// Agent 私有的 prompt 注册表(新 API)
|
|
19
|
+
this._promptRegistry = new PromptRegistry();
|
|
20
|
+
this.promptRegistry = this._promptRegistry;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
// === Status ===
|
|
@@ -28,23 +30,6 @@ class BaseAgent extends EventEmitter {
|
|
|
28
30
|
return this;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
// === Prompt Parts ===
|
|
32
|
-
|
|
33
|
-
registerPromptPart(name, priority, provider) {
|
|
34
|
-
this._promptBuilder.register(name, priority, provider);
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
unregisterPromptPart(name) {
|
|
39
|
-
this._promptBuilder.unregister(name);
|
|
40
|
-
return this;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
invalidatePromptPart(name) {
|
|
44
|
-
this._promptBuilder.invalidate(name);
|
|
45
|
-
return this;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
33
|
// === Destroy ===
|
|
49
34
|
|
|
50
35
|
destroy() {
|
package/src/agent/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const { MainAgent } = require('./main');
|
|
|
5
5
|
const { SubAgent } = require('./sub');
|
|
6
6
|
const { WorkerAgent } = require('./worker');
|
|
7
7
|
const { SystemPromptBuilder } = require('./prompt');
|
|
8
|
+
const { PromptRegistry } = require('./prompt-registry');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Create a MainAgent instance
|
|
@@ -51,6 +52,7 @@ function createSubAgent(framework, config = {}) {
|
|
|
51
52
|
framework,
|
|
52
53
|
maxRetries: config.maxRetries,
|
|
53
54
|
disableTools: config.disableTools,
|
|
55
|
+
hidden: config.hidden,
|
|
54
56
|
});
|
|
55
57
|
_syncPromptParts(framework, subagent);
|
|
56
58
|
framework._agents.push(subagent);
|
|
@@ -84,16 +86,25 @@ function _mergeAIConfig(framework, target) {
|
|
|
84
86
|
|
|
85
87
|
/**
|
|
86
88
|
* Sync main agent prompt parts to subagent
|
|
89
|
+
* 从 framework.promptRegistry 同步到 subagent.promptRegistry
|
|
87
90
|
*/
|
|
88
91
|
function _syncPromptParts(framework, subagent) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
for (const
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
const reg = framework?.promptRegistry;
|
|
93
|
+
if (!reg || reg.count() === 0) return;
|
|
94
|
+
const subReg = subagent?.promptRegistry || subagent?._promptRegistry;
|
|
95
|
+
for (const part of reg.list()) {
|
|
96
|
+
// 跳过主 Agent 私有的 5 个内置 part
|
|
97
|
+
if (part.owner === '_main' && ['original-prompt', 'shared-prompt', 'datetime', 'metadata', 'capabilities'].includes(part.name)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// 已同步过则跳过
|
|
101
|
+
if (subReg && subReg.has(part.owner, part.name)) continue;
|
|
102
|
+
if (subReg) {
|
|
103
|
+
subReg.register(part.owner, part.name, part.provider, {
|
|
104
|
+
slot: part.slot,
|
|
105
|
+
order: part.order,
|
|
106
|
+
description: part.description,
|
|
107
|
+
});
|
|
97
108
|
}
|
|
98
109
|
}
|
|
99
110
|
}
|
|
@@ -104,6 +115,7 @@ module.exports = {
|
|
|
104
115
|
SubAgent,
|
|
105
116
|
WorkerAgent,
|
|
106
117
|
SystemPromptBuilder,
|
|
118
|
+
PromptRegistry,
|
|
107
119
|
createAgent,
|
|
108
120
|
createSubAgent,
|
|
109
121
|
createSessionAgent,
|
package/src/agent/main.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const { BaseAgent } = require('./base');
|
|
9
9
|
const { Logger, LOG_LEVELS } = require('../common/logger');
|
|
10
|
-
const { PROMPT_PRIORITY, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_TEMPERATURE } = require('../common/constants');
|
|
10
|
+
const { PROMPT_PRIORITY, PROMPT_PRIORITY_TO_SLOT, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_TEMPERATURE } = require('../common/constants');
|
|
11
11
|
const { SystemPromptBuilder } = require('./prompt');
|
|
12
12
|
const os = require('os');
|
|
13
13
|
|
|
@@ -44,6 +44,10 @@ class MainAgent extends BaseAgent {
|
|
|
44
44
|
this._skipSyncTools = config.skipSyncTools || false;
|
|
45
45
|
this._subAgents = new Map();
|
|
46
46
|
this._systemPromptBuilder = new SystemPromptBuilder();
|
|
47
|
+
this._systemPromptCache = null; // 确保首次调用 _buildSystemPrompt 不走早期返回
|
|
48
|
+
this._systemPromptDirty = true;
|
|
49
|
+
// 新注册表:默认 part 注册到 framework.promptRegistry(带 slot)
|
|
50
|
+
// _systemPromptBuilder 保留为降级兜底
|
|
47
51
|
this._registerDefaultPromptParts();
|
|
48
52
|
this.systemPrompt = this._buildSystemPrompt();
|
|
49
53
|
|
|
@@ -54,25 +58,48 @@ class MainAgent extends BaseAgent {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
_registerDefaultPromptParts() {
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
// 优先注册到 framework.promptRegistry(带 slot 的新路径)
|
|
62
|
+
// 同时保留 _systemPromptBuilder 的旧路径以兼容老 Plugin.registerPromptPart
|
|
63
|
+
const reg = this.framework?.promptRegistry;
|
|
64
|
+
const MAIN = '_main';
|
|
65
|
+
|
|
66
|
+
const registerBoth = (name, priority, provider, slot) => {
|
|
67
|
+
// 旧路径(降级兜底)
|
|
68
|
+
this._systemPromptBuilder.register(name, priority, provider);
|
|
69
|
+
// 新路径(slot 排序)
|
|
70
|
+
// original-prompt 是 per-Agent 的,不走全局注册表(由 _buildSystemPrompt 直接注入)
|
|
71
|
+
if (name === 'original-prompt') return;
|
|
72
|
+
if (reg && !reg.has(MAIN, name)) {
|
|
73
|
+
reg.register(MAIN, name, provider, {
|
|
74
|
+
slot: slot || PROMPT_PRIORITY_TO_SLOT[name] || 'CUSTOM',
|
|
75
|
+
order: priority,
|
|
76
|
+
description: `内置 part: ${name}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
registerBoth('original-prompt', PROMPT_PRIORITY.ORIGINAL_PROMPT, () => this._originalPrompt, 'IDENTITY');
|
|
82
|
+
registerBoth('shared-prompt', PROMPT_PRIORITY.SHARED_PROMPT, () => {
|
|
59
83
|
return this._sharedPrompt ? this._replacePlaceholders(this._sharedPrompt) : null;
|
|
60
|
-
});
|
|
61
|
-
|
|
84
|
+
}, 'USER');
|
|
85
|
+
registerBoth('metadata', PROMPT_PRIORITY.METADATA, () => {
|
|
62
86
|
const now = new Date();
|
|
63
87
|
const timeStr = now.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
64
88
|
const dateStr = now.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai', weekday: 'long' });
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
const metaParts = ['【运行环境】'];
|
|
90
|
+
metaParts.push(`- 当前时间: ${timeStr}(${dateStr})`);
|
|
91
|
+
metaParts.push(`- 工作目录: ${this.framework?.getCwd?.() ?? process.cwd()}`);
|
|
92
|
+
metaParts.push(`- 平台: ${os.platform()} ${os.release()}`);
|
|
93
|
+
metaParts.push(`- 主机: ${os.hostname()}`);
|
|
94
|
+
// 插件显式设置的元数据
|
|
95
|
+
if (this._metadata.size > 0) {
|
|
96
|
+
for (const [key, value] of this._metadata) {
|
|
97
|
+
metaParts.push(`- ${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
|
|
98
|
+
}
|
|
72
99
|
}
|
|
73
100
|
return metaParts.join('\n');
|
|
74
|
-
});
|
|
75
|
-
|
|
101
|
+
}, 'USER');
|
|
102
|
+
registerBoth('capabilities', PROMPT_PRIORITY.CAPABILITIES, () => this._buildCapabilitiesDescription(), 'CAPABILITY');
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
_getMetadataValue(key) {
|
|
@@ -97,18 +124,59 @@ class MainAgent extends BaseAgent {
|
|
|
97
124
|
if (!plugins || plugins.length === 0) return '';
|
|
98
125
|
const keyPlugins = plugins.filter(p => p.name !== 'defaults' && p.name !== 'agent');
|
|
99
126
|
if (keyPlugins.length === 0) return '';
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
127
|
+
|
|
128
|
+
// 按类别分组,输出紧凑列表(工具列表已在 API 层描述,这里仅给出概览)
|
|
129
|
+
// const categories = {
|
|
130
|
+
// core: [], executor: [], io: [], messaging: [], other: [],
|
|
131
|
+
// };
|
|
132
|
+
// const catMap = {
|
|
133
|
+
// ai: 'core', storage: 'core', 'skill-manager': 'core', 'subagent-manager': 'core',
|
|
134
|
+
// session: 'core', scheduler: 'core', workflow: 'core', rules: 'core', audit: 'core',
|
|
135
|
+
// tools: 'core', 'plugin-manager': 'core', install: 'core', 'python-loader': 'core',
|
|
136
|
+
// 'data-splitter': 'core', coordinator: 'core',
|
|
137
|
+
// shell: 'executor', python: 'executor', extension: 'executor', mcp: 'executor',
|
|
138
|
+
// 'file-system': 'io', web: 'io',
|
|
139
|
+
// telegram: 'messaging', qq: 'messaging', weixin: 'messaging', feishu: 'messaging', email: 'messaging',
|
|
140
|
+
// };
|
|
141
|
+
// for (const p of keyPlugins) {
|
|
142
|
+
// const name = p.instance?.name || p.name || 'unknown';
|
|
143
|
+
// const cat = catMap[name] || 'other';
|
|
144
|
+
// categories[cat].push(name);
|
|
145
|
+
// }
|
|
146
|
+
const lines = [];
|
|
147
|
+
// const lines = ['## 插件概览'];
|
|
148
|
+
// const catLabels = { core: '核心', executor: '执行器', io: '文件/网络', messaging: '消息', other: '其他' };
|
|
149
|
+
// for (const [cat, names] of Object.entries(categories)) {
|
|
150
|
+
// if (names.length === 0) continue;
|
|
151
|
+
// lines.push(`- **${catLabels[cat]}**: ${names.sort().join(', ')}`);
|
|
152
|
+
// }
|
|
106
153
|
return lines.join('\n');
|
|
107
154
|
}
|
|
108
155
|
|
|
109
156
|
_buildSystemPrompt() {
|
|
110
157
|
if (!this._systemPromptDirty && this._systemPromptCache !== null) return this._systemPromptCache;
|
|
111
|
-
|
|
158
|
+
|
|
159
|
+
// 1. Agent 自己的 originalPrompt(per-Agent)
|
|
160
|
+
const parts = [this._originalPrompt];
|
|
161
|
+
|
|
162
|
+
// 2. 全局注册表(skills, tools, extensions 等共享部分,跳过 _main 的 original-prompt)
|
|
163
|
+
const reg = this.framework?.promptRegistry;
|
|
164
|
+
if (reg && reg.count() > 0) {
|
|
165
|
+
try {
|
|
166
|
+
for (const part of reg.list()) {
|
|
167
|
+
if (part.owner === '_main' && part.name === 'original-prompt') continue;
|
|
168
|
+
const content = part.get();
|
|
169
|
+
if (content && content.trim()) parts.push(content.trim());
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
// 注册表 build 失败时降级到旧 builder
|
|
173
|
+
this._systemPromptCache = this._systemPromptBuilder.build();
|
|
174
|
+
this._systemPromptDirty = false;
|
|
175
|
+
return this._systemPromptCache;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this._systemPromptCache = parts.join('\n\n');
|
|
112
180
|
this._systemPromptDirty = false;
|
|
113
181
|
return this._systemPromptCache;
|
|
114
182
|
}
|
|
@@ -116,6 +184,7 @@ class MainAgent extends BaseAgent {
|
|
|
116
184
|
_invalidateSystemPromptCache() {
|
|
117
185
|
this._systemPromptDirty = true;
|
|
118
186
|
this._systemPromptBuilder.invalidateAll();
|
|
187
|
+
this.framework?.promptRegistry?.invalidateAll();
|
|
119
188
|
}
|
|
120
189
|
|
|
121
190
|
_refreshContext() {
|
|
@@ -158,14 +227,22 @@ class MainAgent extends BaseAgent {
|
|
|
158
227
|
}
|
|
159
228
|
|
|
160
229
|
registerPromptPart(name, priority, provider) {
|
|
161
|
-
|
|
230
|
+
// [DEPRECATED] 推荐使用 framework.prompts.register(this.name, ...)
|
|
231
|
+
const reg = this.framework?.promptRegistry;
|
|
232
|
+
if (reg && !reg.has(this.name, name)) {
|
|
233
|
+
reg.register(this.name, name, provider, {
|
|
234
|
+
slot: PROMPT_PRIORITY_TO_SLOT[name] || 'CUSTOM',
|
|
235
|
+
order: priority,
|
|
236
|
+
description: `Legacy registerPromptPart from ${this.name}`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
162
239
|
this._invalidateSystemPromptCache();
|
|
163
240
|
this._refreshContext();
|
|
164
241
|
return this;
|
|
165
242
|
}
|
|
166
243
|
|
|
167
244
|
unregisterPromptPart(name) {
|
|
168
|
-
this.
|
|
245
|
+
this.framework?.promptRegistry?.unregister(this.name, name);
|
|
169
246
|
this._invalidateSystemPromptCache();
|
|
170
247
|
this._refreshContext();
|
|
171
248
|
return this;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PromptRegistry — 全局系统提示词注册表
|
|
5
|
+
*
|
|
6
|
+
* 设计目标:
|
|
7
|
+
* - 单一注册入口(替代散落在各 Plugin 的 registerPromptPart 调用)
|
|
8
|
+
* - owner 命名空间隔离(避免 part 名冲突)
|
|
9
|
+
* - 用语义槽位(PROMPT_SLOT)排序,替代魔法数字
|
|
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 完全切换到本注册表
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const { EventEmitter } = require('../common/events');
|
|
24
|
+
const { PROMPT_SLOT } = require('../common/constants');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 单个 prompt part
|
|
28
|
+
* - provider 是 () => string|null,返回 null/空串时被 build 跳过
|
|
29
|
+
* - 内部带缓存,失效后才重新计算
|
|
30
|
+
*/
|
|
31
|
+
class PromptPart {
|
|
32
|
+
constructor(owner, name, provider, options = {}) {
|
|
33
|
+
this.owner = owner;
|
|
34
|
+
this.name = name;
|
|
35
|
+
this.provider = provider;
|
|
36
|
+
this.slot = options.slot || 'CUSTOM';
|
|
37
|
+
this.order = options.order ?? null; // 显式 order 覆盖 slot 默认
|
|
38
|
+
this.description = options.description || '';
|
|
39
|
+
this.metadata = options.metadata || {};
|
|
40
|
+
this._cached = null;
|
|
41
|
+
this._dirty = true;
|
|
42
|
+
this._error = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get() {
|
|
46
|
+
if (this._dirty || this._cached === null) {
|
|
47
|
+
try {
|
|
48
|
+
const result = this.provider();
|
|
49
|
+
this._cached = result == null ? null : String(result);
|
|
50
|
+
this._dirty = false;
|
|
51
|
+
this._error = null;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
this._error = e;
|
|
54
|
+
this._cached = null;
|
|
55
|
+
this._dirty = false;
|
|
56
|
+
// 警告但不抛错 — 避免单个 part 失败导致整个 prompt 渲染失败
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.warn(`[PromptRegistry:${this.owner}::${this.name}] provider failed:`, e.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return this._cached;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
invalidate() {
|
|
65
|
+
this._dirty = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* PromptRegistry 主类
|
|
71
|
+
*/
|
|
72
|
+
class PromptRegistry extends EventEmitter {
|
|
73
|
+
constructor(options = {}) {
|
|
74
|
+
super();
|
|
75
|
+
|
|
76
|
+
// key = "owner::name"
|
|
77
|
+
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 时检测循环依赖
|
|
93
|
+
this._building = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
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
|
+
// ==================== 注册/注销 ====================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 注册一个 prompt part
|
|
119
|
+
* @param {string} owner 拥有者标识(通常是 plugin name 或 '_main')
|
|
120
|
+
* @param {string} name part 名(在 owner 内唯一)
|
|
121
|
+
* @param {Function} provider () => string|null
|
|
122
|
+
* @param {Object} [options]
|
|
123
|
+
* @param {string} [options.slot='CUSTOM'] 槽位名
|
|
124
|
+
* @param {number} [options.order] 自定义 order(覆盖 slot 默认)
|
|
125
|
+
* @param {string} [options.description] 描述(用于 list/debug)
|
|
126
|
+
* @param {Object} [options.metadata] 额外元数据
|
|
127
|
+
* @returns {PromptPart}
|
|
128
|
+
*/
|
|
129
|
+
register(owner, name, provider, options = {}) {
|
|
130
|
+
if (!owner || typeof owner !== 'string') {
|
|
131
|
+
throw new Error('PromptRegistry.register: owner is required');
|
|
132
|
+
}
|
|
133
|
+
if (!name || typeof name !== 'string') {
|
|
134
|
+
throw new Error('PromptRegistry.register: name is required');
|
|
135
|
+
}
|
|
136
|
+
if (typeof provider !== 'function') {
|
|
137
|
+
throw new Error('PromptRegistry.register: provider must be a function');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const key = `${owner}::${name}`;
|
|
141
|
+
const part = new PromptPart(owner, name, provider, options);
|
|
142
|
+
this._parts.set(key, part);
|
|
143
|
+
this.emit('register', { owner, name, slot: part.slot });
|
|
144
|
+
return part;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 注销一个 part
|
|
149
|
+
* @returns {boolean} 是否真的删除了
|
|
150
|
+
*/
|
|
151
|
+
unregister(owner, name) {
|
|
152
|
+
const key = `${owner}::${name}`;
|
|
153
|
+
const existed = this._parts.delete(key);
|
|
154
|
+
if (existed) this.emit('unregister', { owner, name });
|
|
155
|
+
return existed;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 清空指定 owner 的所有 parts
|
|
160
|
+
* @returns {number} 删除的 part 数
|
|
161
|
+
*/
|
|
162
|
+
clearOwner(owner) {
|
|
163
|
+
const prefix = `${owner}::`;
|
|
164
|
+
let count = 0;
|
|
165
|
+
for (const key of Array.from(this._parts.keys())) {
|
|
166
|
+
if (key.startsWith(prefix)) {
|
|
167
|
+
this._parts.delete(key);
|
|
168
|
+
count++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (count > 0) this.emit('clear-owner', { owner, count });
|
|
172
|
+
return count;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 失效指定 part(下次 build 时重新计算)
|
|
177
|
+
*/
|
|
178
|
+
invalidate(owner, name) {
|
|
179
|
+
const part = this._parts.get(`${owner}::${name}`);
|
|
180
|
+
if (part) {
|
|
181
|
+
part.invalidate();
|
|
182
|
+
this.emit('invalidate', { owner, name });
|
|
183
|
+
}
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 失效所有 parts
|
|
189
|
+
*/
|
|
190
|
+
invalidateAll() {
|
|
191
|
+
for (const part of this._parts.values()) part.invalidate();
|
|
192
|
+
this.emit('invalidate-all');
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ==================== 查询 ====================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 获取单个 part 实例
|
|
200
|
+
*/
|
|
201
|
+
getPart(owner, name) {
|
|
202
|
+
return this._parts.get(`${owner}::${name}`) || null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 是否有指定 part
|
|
207
|
+
*/
|
|
208
|
+
has(owner, name) {
|
|
209
|
+
return this._parts.has(`${owner}::${name}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 列出所有 parts(按 order 排序)
|
|
214
|
+
*/
|
|
215
|
+
list() {
|
|
216
|
+
return Array.from(this._parts.values()).sort((a, b) => this._compareOrder(a, b));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 按 owner 过滤
|
|
221
|
+
*/
|
|
222
|
+
listByOwner(owner) {
|
|
223
|
+
return this.list().filter((p) => p.owner === owner);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 按 slot 过滤
|
|
228
|
+
*/
|
|
229
|
+
listBySlot(slot) {
|
|
230
|
+
return this.list().filter((p) => p.slot === slot);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_compareOrder(a, b) {
|
|
234
|
+
const oa = a.order != null ? a.order : this._slots.get(a.slot)?.order ?? 999;
|
|
235
|
+
const ob = b.order != null ? b.order : this._slots.get(b.slot)?.order ?? 999;
|
|
236
|
+
if (oa !== ob) return oa - ob;
|
|
237
|
+
// 同 order 时按 owner → name 字母序,保证稳定
|
|
238
|
+
if (a.owner !== b.owner) return a.owner.localeCompare(b.owner);
|
|
239
|
+
return a.name.localeCompare(b.name);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
count() {
|
|
243
|
+
return this._parts.size;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ==================== 渲染 ====================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 渲染完整 prompt 文本(按 slot order 拼接)
|
|
250
|
+
*/
|
|
251
|
+
build() {
|
|
252
|
+
if (this._building) {
|
|
253
|
+
throw new Error('PromptRegistry.build() re-entered — check provider for circular calls');
|
|
254
|
+
}
|
|
255
|
+
this._building = true;
|
|
256
|
+
try {
|
|
257
|
+
const sections = [];
|
|
258
|
+
for (const part of this.list()) {
|
|
259
|
+
const content = part.get();
|
|
260
|
+
if (content && content.trim()) sections.push(content.trim());
|
|
261
|
+
}
|
|
262
|
+
return sections.join('\n\n');
|
|
263
|
+
} finally {
|
|
264
|
+
this._building = false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* build() 的语义化别名,调试用
|
|
270
|
+
*/
|
|
271
|
+
preview() {
|
|
272
|
+
return this.build();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 渲染结构化清单(调试 / 文档生成)
|
|
277
|
+
* 返回 [{ owner, name, slot, order, length, contentPreview }]
|
|
278
|
+
*/
|
|
279
|
+
inspect() {
|
|
280
|
+
return this.list().map((part) => {
|
|
281
|
+
const content = part.get();
|
|
282
|
+
return {
|
|
283
|
+
owner: part.owner,
|
|
284
|
+
name: part.name,
|
|
285
|
+
slot: part.slot,
|
|
286
|
+
order: part.order != null ? part.order : this._slots.get(part.slot)?.order ?? 999,
|
|
287
|
+
description: part.description,
|
|
288
|
+
hasContent: Boolean(content && content.trim()),
|
|
289
|
+
length: content ? content.length : 0,
|
|
290
|
+
contentPreview: content ? content.slice(0, 80).replace(/\n/g, ' ') : null,
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = { PromptRegistry, PromptPart };
|
package/src/agent/sub-config.js
CHANGED
|
@@ -6,84 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 解析 YAML frontmatter
|
|
12
|
-
*/
|
|
13
|
-
function parseFrontmatter(content) {
|
|
14
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
15
|
-
if (!match) return null;
|
|
16
|
-
|
|
17
|
-
const frontmatter = {};
|
|
18
|
-
const lines = match[1].split('\n');
|
|
19
|
-
let currentKey = null;
|
|
20
|
-
|
|
21
|
-
for (const line of lines) {
|
|
22
|
-
// 处理 metadata 嵌套
|
|
23
|
-
if (currentKey === 'metadata') {
|
|
24
|
-
if (line.match(/^ {2}[a-zA-Z]/)) {
|
|
25
|
-
const colonIndex = line.indexOf(':');
|
|
26
|
-
if (colonIndex > 0) {
|
|
27
|
-
const key = line.substring(2, colonIndex).trim();
|
|
28
|
-
const value = line
|
|
29
|
-
.substring(colonIndex + 1)
|
|
30
|
-
.trim()
|
|
31
|
-
.replace(/^["']|["']$/g, '');
|
|
32
|
-
frontmatter[currentKey][key] = value;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
} else if (line.match(/^[a-zA-Z]/)) {
|
|
36
|
-
currentKey = null;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const colonIndex = line.indexOf(':');
|
|
42
|
-
if (colonIndex === -1) continue;
|
|
43
|
-
|
|
44
|
-
const key = line.substring(0, colonIndex).trim();
|
|
45
|
-
let value = line.substring(colonIndex + 1).trim();
|
|
46
|
-
|
|
47
|
-
// 移除引号
|
|
48
|
-
if (
|
|
49
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
50
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
51
|
-
) {
|
|
52
|
-
value = value.slice(1, -1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (key === 'metadata') {
|
|
56
|
-
currentKey = 'metadata';
|
|
57
|
-
frontmatter.metadata = {};
|
|
58
|
-
} else if (key === 'allowed-tools') {
|
|
59
|
-
frontmatter[key] = value
|
|
60
|
-
.split(',')
|
|
61
|
-
.map((v) => v.trim())
|
|
62
|
-
.filter(Boolean);
|
|
63
|
-
} else if (key === 'skills') {
|
|
64
|
-
frontmatter[key] = value
|
|
65
|
-
.split(',')
|
|
66
|
-
.map((v) => v.trim())
|
|
67
|
-
.filter(Boolean);
|
|
68
|
-
} else if (key === 'tools') {
|
|
69
|
-
frontmatter[key] = value
|
|
70
|
-
.split(',')
|
|
71
|
-
.map((v) => v.trim())
|
|
72
|
-
.filter(Boolean);
|
|
73
|
-
} else {
|
|
74
|
-
frontmatter[key] = value;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return frontmatter;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 移除 frontmatter,只保留正文内容
|
|
83
|
-
*/
|
|
84
|
-
function stripFrontmatter(content) {
|
|
85
|
-
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
|
|
86
|
-
}
|
|
9
|
+
const { parseFrontmatter, stripFrontmatter } = require('../utils');
|
|
87
10
|
|
|
88
11
|
class SubAgentConfig {
|
|
89
12
|
constructor(name, filePath) {
|