foliko 2.0.4 → 2.0.6
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/.claude/settings.local.json +4 -1
- package/.cli_default_systemPrompt.md +291 -0
- package/CLAUDE.md +3 -0
- package/README.md +20 -3
- package/docs/architecture.md +34 -2
- package/docs/extensions.md +199 -0
- package/docs/migration.md +100 -0
- package/docs/public-api.md +1180 -6
- package/docs/usage.md +122 -30
- package/package.json +1 -1
- package/plugins/core/audit/index.js +1 -1
- package/plugins/core/default/bootstrap.js +65 -19
- package/plugins/core/python-loader/index.js +43 -25
- package/plugins/core/skill-manager/PROMPT.md +6 -0
- package/plugins/core/skill-manager/index.js +538 -93
- package/plugins/core/sub-agent/PROMPT.md +10 -0
- package/plugins/core/sub-agent/index.js +36 -3
- package/plugins/core/think/index.js +1 -0
- package/plugins/core/workflow/index.js +106 -22
- package/plugins/executors/data-splitter/PROMPT.md +13 -0
- package/plugins/executors/data-splitter/index.js +5 -4
- package/plugins/executors/extension/extension-registry.js +145 -0
- package/plugins/executors/extension/index.js +405 -437
- package/plugins/executors/extension/prompt-builder.js +359 -0
- package/plugins/executors/extension/skill-helper.js +143 -0
- package/plugins/messaging/feishu/index.js +5 -3
- package/plugins/messaging/qq/index.js +6 -4
- package/plugins/messaging/telegram/index.js +6 -3
- package/plugins/messaging/weixin/index.js +5 -3
- package/plugins/tools/PROMPT.md +26 -0
- package/plugins/tools/index.js +6 -5
- package/skills/foliko/AGENTS.md +196 -43
- package/skills/foliko/SKILL.md +157 -28
- package/skills/mcp/SKILL.md +77 -118
- package/skills/plugins/SKILL.md +89 -3
- package/skills/python/SKILL.md +57 -39
- package/skills/skill-guide/SKILL.md +42 -34
- package/skills/workflows/SKILL.md +224 -9
- package/skills/workflows/workflow-troubleshooting/SKILL.md +221 -281
- package/src/agent/chat.js +48 -27
- package/src/agent/main.js +34 -13
- package/src/agent/prompt-registry.js +133 -87
- package/src/agent/prompts/PROMPT.md +3 -0
- package/src/agent/sub.js +1 -1
- package/src/cli/ui/chat-ui-old.js +5 -2
- package/src/cli/ui/chat-ui.js +5 -2
- package/src/common/constants.js +12 -0
- package/src/common/error-capture.js +91 -0
- package/src/common/logger.js +2 -2
- package/src/context/compressor.js +6 -2
- package/src/executors/mcp-executor.js +105 -125
- package/src/framework/framework.js +632 -6
- package/src/index.js +4 -0
- package/src/plugin/base.js +913 -10
- package/src/plugin/manager.js +29 -8
- package/src/tool/schema.js +32 -9
- package/tests/core/plugin-prompts.test.js +13 -13
- package/tests/core/prompt-registry.test.js +59 -51
- package/website/index.html +821 -0
package/src/agent/chat.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
ToolLoopAgent,
|
|
17
17
|
isLoopFinished,
|
|
18
18
|
} = require('ai');
|
|
19
|
+
const { z } = require('zod');
|
|
19
20
|
const { autoSplitToolResult } = require('../../plugins/executors/data-splitter');
|
|
20
21
|
const { cleanResponse } = require('../utils');
|
|
21
22
|
const { ChatQueueManager } = require('../common/queue');
|
|
@@ -32,6 +33,8 @@ const {
|
|
|
32
33
|
DEFAULT_MAX_CONCURRENT,
|
|
33
34
|
DEFAULT_RETRY_ATTEMPTS,
|
|
34
35
|
DEFAULT_RETRY_DELAY_MS,
|
|
36
|
+
SIMPLE_COMPRESS_TOKEN_RATIO,
|
|
37
|
+
SMART_COMPRESS_TOKEN_RATIO,
|
|
35
38
|
} = require('../common/constants');
|
|
36
39
|
|
|
37
40
|
class AgentChatHandler extends EventEmitter {
|
|
@@ -135,26 +138,22 @@ class AgentChatHandler extends EventEmitter {
|
|
|
135
138
|
this._setupEventForwarding();
|
|
136
139
|
|
|
137
140
|
// 转发 queueManager 事件到 ChatSession(让 SessionScope 能收到 stream:chunk 和 queue:completed 等)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
this.queueManager.on('queue:failed',
|
|
145
|
-
this._chatSession.emit('queue:failed', data);
|
|
146
|
-
});
|
|
141
|
+
// 保存引用以便 destroy 时能正确移除
|
|
142
|
+
this._onStreamChunk = (data) => this._chatSession.emit('stream:chunk', data);
|
|
143
|
+
this._onQueueCompleted = (data) => this._chatSession.emit('queue:completed', data);
|
|
144
|
+
this._onQueueFailed = (data) => this._chatSession.emit('queue:failed', data);
|
|
145
|
+
this.queueManager.on('stream:chunk', this._onStreamChunk);
|
|
146
|
+
this.queueManager.on('queue:completed', this._onQueueCompleted);
|
|
147
|
+
this.queueManager.on('queue:failed', this._onQueueFailed);
|
|
147
148
|
|
|
148
149
|
// 转发 message:complete 和 message:error 到 ChatSession(让 SessionScope 能收到)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.on('message:error',
|
|
156
|
-
this._chatSession.emit('message:error', data);
|
|
157
|
-
});
|
|
150
|
+
// 保存引用以便 destroy 时能正确移除
|
|
151
|
+
this._onMessageStart = (data) => this._chatSession.emit('message:start', data);
|
|
152
|
+
this._onMessageComplete = (data) => this._chatSession.emit('message:complete', data);
|
|
153
|
+
this._onMessageError = (data) => this._chatSession.emit('message:error', data);
|
|
154
|
+
this.on('message:start', this._onMessageStart);
|
|
155
|
+
this.on('message:complete', this._onMessageComplete);
|
|
156
|
+
this.on('message:error', this._onMessageError);
|
|
158
157
|
|
|
159
158
|
// 设置消息处理器(使用 bind 保持 this 引用)
|
|
160
159
|
this._chatSession.setMessageProcessor(this._processMessage.bind(this));
|
|
@@ -476,7 +475,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
476
475
|
throw new Error('AI 客户端未配置');
|
|
477
476
|
}
|
|
478
477
|
this._validateToolCalls(messages);
|
|
479
|
-
const systemPrompt = framework.
|
|
478
|
+
const systemPrompt = framework.prompts.build();
|
|
480
479
|
//await fs.promises.writeFile(`.${sessionId}_systemPrompt.md`, systemPrompt); // 调试用:保存系统提示词
|
|
481
480
|
const tools = this._getAITools(aiTool);
|
|
482
481
|
const agent = new ToolLoopAgent({
|
|
@@ -628,6 +627,14 @@ class AgentChatHandler extends EventEmitter {
|
|
|
628
627
|
* 销毁
|
|
629
628
|
*/
|
|
630
629
|
destroy() {
|
|
630
|
+
// Remove listeners from queueManager
|
|
631
|
+
this.queueManager.off('stream:chunk', this._onStreamChunk);
|
|
632
|
+
this.queueManager.off('queue:completed', this._onQueueCompleted);
|
|
633
|
+
this.queueManager.off('queue:failed', this._onQueueFailed);
|
|
634
|
+
// Remove listeners from this (message forwarding)
|
|
635
|
+
this.off('message:start', this._onMessageStart);
|
|
636
|
+
this.off('message:complete', this._onMessageComplete);
|
|
637
|
+
this.off('message:error', this._onMessageError);
|
|
631
638
|
if (this._chatSession) {
|
|
632
639
|
this._chatSession.removeAllListeners();
|
|
633
640
|
}
|
|
@@ -659,7 +666,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
659
666
|
throw new Error('AI 客户端未配置');
|
|
660
667
|
}
|
|
661
668
|
this._validateToolCalls(messages);
|
|
662
|
-
const systemPrompt = framework.
|
|
669
|
+
const systemPrompt = framework.prompts.build();
|
|
663
670
|
const tools = this._getAITools(aiTool);
|
|
664
671
|
|
|
665
672
|
// DeepSeek thinking mode: 处理参数
|
|
@@ -763,7 +770,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
763
770
|
const toolsTokens = this._getCachedToolsTokens();
|
|
764
771
|
const systemPromptTokens = this._getCachedSystemPromptTokens();
|
|
765
772
|
const totalTokens = cachedMsgTokens + toolsTokens + systemPromptTokens;
|
|
766
|
-
const limit = this._maxContextTokens *
|
|
773
|
+
const limit = this._maxContextTokens * SIMPLE_COMPRESS_TOKEN_RATIO;
|
|
767
774
|
|
|
768
775
|
// logger.info(`BEFORE: messages=${messages.length}, tokens=${totalTokens}/${limit}`);
|
|
769
776
|
|
|
@@ -924,8 +931,8 @@ class AgentChatHandler extends EventEmitter {
|
|
|
924
931
|
name: toolName,
|
|
925
932
|
description: toolDef.description || '',
|
|
926
933
|
execute: async (args) => {
|
|
927
|
-
//
|
|
928
|
-
const cleanedArgs = this._cleanToolArgs(args);
|
|
934
|
+
// 清理参数并验证类型
|
|
935
|
+
const cleanedArgs = this._cleanToolArgs(args, toolDef.inputSchema);
|
|
929
936
|
|
|
930
937
|
// 执行工具
|
|
931
938
|
this.emit('tool-call', { name: toolName, args: cleanedArgs });
|
|
@@ -954,7 +961,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
954
961
|
} catch (err) {
|
|
955
962
|
this.emit('tool-error', { name: toolName, args: cleanedArgs, error: err.message });
|
|
956
963
|
// 返回错误信息字符串,而不是抛出异常
|
|
957
|
-
return { success: false,error: err.message}
|
|
964
|
+
return { success: false, error: err.message }
|
|
958
965
|
}
|
|
959
966
|
},
|
|
960
967
|
};
|
|
@@ -976,10 +983,13 @@ class AgentChatHandler extends EventEmitter {
|
|
|
976
983
|
}
|
|
977
984
|
|
|
978
985
|
/**
|
|
979
|
-
*
|
|
986
|
+
* 清理工具参数并根据 inputSchema 验证类型
|
|
980
987
|
* @private
|
|
988
|
+
* @param {Object} args - 工具参数
|
|
989
|
+
* @param {Object} inputSchema - Zod schema (optional)
|
|
990
|
+
* @returns {Object} 清理后的参数
|
|
981
991
|
*/
|
|
982
|
-
_cleanToolArgs(args) {
|
|
992
|
+
_cleanToolArgs(args, inputSchema) {
|
|
983
993
|
if (!args || typeof args !== 'object') {
|
|
984
994
|
return {};
|
|
985
995
|
}
|
|
@@ -991,6 +1001,17 @@ class AgentChatHandler extends EventEmitter {
|
|
|
991
1001
|
}
|
|
992
1002
|
cleaned[key] = value;
|
|
993
1003
|
}
|
|
1004
|
+
|
|
1005
|
+
// Validate against inputSchema if provided and is a Zod schema
|
|
1006
|
+
if (inputSchema && inputSchema._def && inputSchema.parse) {
|
|
1007
|
+
try {
|
|
1008
|
+
return inputSchema.parse(cleaned);
|
|
1009
|
+
} catch (err) {
|
|
1010
|
+
// If Zod validation fails, return cleaned args (backward compat)
|
|
1011
|
+
logger.warn(`[Tool] Args validation failed: ${err.message}`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
994
1015
|
return cleaned;
|
|
995
1016
|
}
|
|
996
1017
|
|
|
@@ -1002,7 +1023,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
1002
1023
|
return async ({ stepNumber, messages: inputMessages }) => {
|
|
1003
1024
|
try {
|
|
1004
1025
|
const tokenCount = this._countMessagesTokens(inputMessages);
|
|
1005
|
-
const tokenLimit = this._maxContextTokens *
|
|
1026
|
+
const tokenLimit = this._maxContextTokens * SMART_COMPRESS_TOKEN_RATIO;
|
|
1006
1027
|
|
|
1007
1028
|
// 超过限制时,保留三分之一的消息
|
|
1008
1029
|
if (tokenCount > tokenLimit) {
|
package/src/agent/main.js
CHANGED
|
@@ -99,7 +99,29 @@ class MainAgent extends BaseAgent {
|
|
|
99
99
|
}
|
|
100
100
|
return metaParts.join('\n');
|
|
101
101
|
}, 'USER');
|
|
102
|
-
|
|
102
|
+
|
|
103
|
+
// capabilities 从文件加载(动态内容通过 interpolate 注入)
|
|
104
|
+
const capsProvider = () => {
|
|
105
|
+
const list = this._buildCapabilitiesList();
|
|
106
|
+
return list ? `## 系统能力\n\n${list}` : '';
|
|
107
|
+
};
|
|
108
|
+
registerBoth('capabilities', PROMPT_PRIORITY.CAPABILITIES, capsProvider, 'CAPABILITY');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_buildCapabilitiesList() {
|
|
112
|
+
const plugins = this.framework.pluginManager.getAll();
|
|
113
|
+
if (!plugins || plugins.length === 0) return '';
|
|
114
|
+
const keyPlugins = plugins.filter(p => p.name !== 'defaults' && p.name !== 'agent');
|
|
115
|
+
if (keyPlugins.length === 0) return '';
|
|
116
|
+
|
|
117
|
+
const lines = [];
|
|
118
|
+
keyPlugins.forEach((plugin) => {
|
|
119
|
+
const name = plugin.instance?.name || plugin.name || 'unknown';
|
|
120
|
+
const description = plugin.instance?.description || '无描述';
|
|
121
|
+
lines.push(`- **${name}**:${description}`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return lines.join('\n');
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
_getMetadataValue(key) {
|
|
@@ -156,27 +178,26 @@ class MainAgent extends BaseAgent {
|
|
|
156
178
|
_buildSystemPrompt() {
|
|
157
179
|
if (!this._systemPromptDirty && this._systemPromptCache !== null) return this._systemPromptCache;
|
|
158
180
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// 2. 全局注册表(skills, tools, extensions 等共享部分,跳过 _main 的 original-prompt)
|
|
163
|
-
const reg = this.framework?.promptRegistry;
|
|
164
|
-
if (reg && reg.count() > 0) {
|
|
181
|
+
// 统一走 framework.prompts.build():自动包含 _originalPrompt + 注册表所有 parts
|
|
182
|
+
// (PromptRegistry.build() 已处理两段拼接,PromptPart 自带 _dirty 缓存)
|
|
183
|
+
if (this.framework?.prompts?.build) {
|
|
165
184
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
185
|
+
const text = this.framework.prompts.build();
|
|
186
|
+
if (text) {
|
|
187
|
+
this._systemPromptCache = text;
|
|
188
|
+
this._systemPromptDirty = false;
|
|
189
|
+
return this._systemPromptCache;
|
|
170
190
|
}
|
|
171
191
|
} catch (err) {
|
|
172
|
-
//
|
|
192
|
+
// 降级到旧 builder
|
|
173
193
|
this._systemPromptCache = this._systemPromptBuilder.build();
|
|
174
194
|
this._systemPromptDirty = false;
|
|
175
195
|
return this._systemPromptCache;
|
|
176
196
|
}
|
|
177
197
|
}
|
|
178
198
|
|
|
179
|
-
|
|
199
|
+
// 没有 framework 时:只返回 _originalPrompt
|
|
200
|
+
this._systemPromptCache = this._originalPrompt || '';
|
|
180
201
|
this._systemPromptDirty = false;
|
|
181
202
|
return this._systemPromptCache;
|
|
182
203
|
}
|
|
@@ -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,31 @@ class PromptPart {
|
|
|
70
57
|
* PromptRegistry 主类
|
|
71
58
|
*/
|
|
72
59
|
class PromptRegistry extends EventEmitter {
|
|
73
|
-
constructor(
|
|
60
|
+
constructor(framework = null) {
|
|
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;
|
|
66
|
+
// 关联 framework(用于 build 时自动追加 main agent 的 originalPrompt)
|
|
67
|
+
this._framework = framework;
|
|
94
68
|
}
|
|
95
69
|
|
|
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);
|
|
70
|
+
setFramework(framework) {
|
|
71
|
+
this._framework = framework;
|
|
113
72
|
}
|
|
114
73
|
|
|
115
74
|
// ==================== 注册/注销 ====================
|
|
116
75
|
|
|
117
76
|
/**
|
|
118
77
|
* 注册一个 prompt part
|
|
119
|
-
* @param {string} owner
|
|
78
|
+
* @param {string} owner 拥有者标识
|
|
120
79
|
* @param {string} name part 名(在 owner 内唯一)
|
|
121
80
|
* @param {Function} provider () => string|null
|
|
122
81
|
* @param {Object} [options]
|
|
123
|
-
* @param {string} [options.
|
|
124
|
-
* @param {number} [options.
|
|
125
|
-
* @param {string} [options.description]
|
|
126
|
-
* @param {Object} [options.metadata] 额外元数据
|
|
127
|
-
* @returns {PromptPart}
|
|
82
|
+
* @param {string} [options.scope='global'] 作用域: 'global'|'main'|agentId
|
|
83
|
+
* @param {number} [options.priority=100] 优先级(越小越靠前)
|
|
84
|
+
* @param {string} [options.description] 描述
|
|
128
85
|
*/
|
|
129
86
|
register(owner, name, provider, options = {}) {
|
|
130
87
|
if (!owner || typeof owner !== 'string') {
|
|
@@ -140,13 +97,59 @@ class PromptRegistry extends EventEmitter {
|
|
|
140
97
|
const key = `${owner}::${name}`;
|
|
141
98
|
const part = new PromptPart(owner, name, provider, options);
|
|
142
99
|
this._parts.set(key, part);
|
|
143
|
-
this.emit('register', { owner, name,
|
|
100
|
+
this.emit('register', { owner, name, scope: part.scope, priority: part.priority });
|
|
144
101
|
return part;
|
|
145
102
|
}
|
|
146
103
|
|
|
104
|
+
/**
|
|
105
|
+
* 注册一个 prompt part(从文件读取,支持变量插值)
|
|
106
|
+
* @param {string} owner 拥有者标识
|
|
107
|
+
* @param {string} filePath 文件路径
|
|
108
|
+
* @param {Object|Function} [vars] 变量对象或函数,模板中用 {{varName}} 引用
|
|
109
|
+
* - Object: 静态变量,注册时替换
|
|
110
|
+
* - Function: 动态变量,每次渲染时调用函数获取最新值
|
|
111
|
+
* @param {Object} [options] 同 register 选项
|
|
112
|
+
*/
|
|
113
|
+
registerFile(owner, filePath, vars = null, options = {}) {
|
|
114
|
+
if (!owner || typeof owner !== 'string') {
|
|
115
|
+
throw new Error('PromptRegistry.registerFile: owner is required');
|
|
116
|
+
}
|
|
117
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
118
|
+
throw new Error('PromptRegistry.registerFile: filePath is required');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 读取文件内容
|
|
122
|
+
const fullPath = path.resolve(filePath);
|
|
123
|
+
if (!fs.existsSync(fullPath)) {
|
|
124
|
+
throw new Error(`PromptRegistry.registerFile: file not found: ${fullPath}`);
|
|
125
|
+
}
|
|
126
|
+
const rawContent = fs.readFileSync(fullPath, 'utf-8');
|
|
127
|
+
|
|
128
|
+
// 转为 provider,渲染时动态插值
|
|
129
|
+
const provider = () => {
|
|
130
|
+
let content = rawContent;
|
|
131
|
+
if (vars) {
|
|
132
|
+
const varsObj = typeof vars === 'function' ? vars() : vars;
|
|
133
|
+
if (varsObj && typeof varsObj === 'object') {
|
|
134
|
+
content = content.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
|
135
|
+
if (Object.prototype.hasOwnProperty.call(varsObj, varName)) {
|
|
136
|
+
return String(varsObj[varName]);
|
|
137
|
+
}
|
|
138
|
+
return match; // 未找到的变量保留原样
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return content;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// 从文件名生成 name
|
|
146
|
+
const name = options.name || path.basename(filePath, path.extname(filePath));
|
|
147
|
+
|
|
148
|
+
return this.register(owner, name, provider, options);
|
|
149
|
+
}
|
|
150
|
+
|
|
147
151
|
/**
|
|
148
152
|
* 注销一个 part
|
|
149
|
-
* @returns {boolean} 是否真的删除了
|
|
150
153
|
*/
|
|
151
154
|
unregister(owner, name) {
|
|
152
155
|
const key = `${owner}::${name}`;
|
|
@@ -157,7 +160,6 @@ class PromptRegistry extends EventEmitter {
|
|
|
157
160
|
|
|
158
161
|
/**
|
|
159
162
|
* 清空指定 owner 的所有 parts
|
|
160
|
-
* @returns {number} 删除的 part 数
|
|
161
163
|
*/
|
|
162
164
|
clearOwner(owner) {
|
|
163
165
|
const prefix = `${owner}::`;
|
|
@@ -173,7 +175,7 @@ class PromptRegistry extends EventEmitter {
|
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
/**
|
|
176
|
-
* 失效指定 part
|
|
178
|
+
* 失效指定 part
|
|
177
179
|
*/
|
|
178
180
|
invalidate(owner, name) {
|
|
179
181
|
const part = this._parts.get(`${owner}::${name}`);
|
|
@@ -210,10 +212,14 @@ class PromptRegistry extends EventEmitter {
|
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
/**
|
|
213
|
-
* 列出所有 parts(按
|
|
215
|
+
* 列出所有 parts(按 priority 排序)
|
|
214
216
|
*/
|
|
215
217
|
list() {
|
|
216
|
-
return Array.from(this._parts.values()).sort((a, b) =>
|
|
218
|
+
return Array.from(this._parts.values()).sort((a, b) => {
|
|
219
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
220
|
+
if (a.owner !== b.owner) return a.owner.localeCompare(b.owner);
|
|
221
|
+
return a.name.localeCompare(b.name);
|
|
222
|
+
});
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
/**
|
|
@@ -224,19 +230,24 @@ class PromptRegistry extends EventEmitter {
|
|
|
224
230
|
}
|
|
225
231
|
|
|
226
232
|
/**
|
|
227
|
-
* 按
|
|
233
|
+
* 按 scope 过滤
|
|
228
234
|
*/
|
|
229
|
-
|
|
230
|
-
return this.list().filter((p) => p.
|
|
235
|
+
listByScope(scope) {
|
|
236
|
+
return this.list().filter((p) => p.scope === scope);
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return
|
|
239
|
+
/**
|
|
240
|
+
* 获取适用于指定 agent 的 parts
|
|
241
|
+
* @param {string|null} agentId agent ID,null 表示主 agent
|
|
242
|
+
*/
|
|
243
|
+
listForAgent(agentId = null) {
|
|
244
|
+
const isMain = agentId === null || agentId === 'main';
|
|
245
|
+
return this.list().filter((p) => {
|
|
246
|
+
if (p.scope === 'global') return true;
|
|
247
|
+
if (p.scope === 'main' && isMain) return true;
|
|
248
|
+
if (p.scope === agentId) return true;
|
|
249
|
+
return false;
|
|
250
|
+
});
|
|
240
251
|
}
|
|
241
252
|
|
|
242
253
|
count() {
|
|
@@ -246,16 +257,39 @@ class PromptRegistry extends EventEmitter {
|
|
|
246
257
|
// ==================== 渲染 ====================
|
|
247
258
|
|
|
248
259
|
/**
|
|
249
|
-
* 渲染完整 prompt
|
|
260
|
+
* 渲染完整 prompt(按 priority 拼接)
|
|
261
|
+
* 自动包含 main agent 的 originalPrompt(作为首段)
|
|
250
262
|
*/
|
|
251
263
|
build() {
|
|
264
|
+
return this.buildForAgent(null);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 渲染适用于指定 agent 的 prompt
|
|
269
|
+
* - main agent (agentId 为 null 或 'main'):自动在首段追加 `_originalPrompt`
|
|
270
|
+
* - 其他 agent:不追加(subagent 自己有 _customSystemPrompt)
|
|
271
|
+
*
|
|
272
|
+
* @param {string|null} agentId agent ID
|
|
273
|
+
*/
|
|
274
|
+
buildForAgent(agentId = null) {
|
|
252
275
|
if (this._building) {
|
|
253
276
|
throw new Error('PromptRegistry.build() re-entered — check provider for circular calls');
|
|
254
277
|
}
|
|
255
278
|
this._building = true;
|
|
256
279
|
try {
|
|
257
280
|
const sections = [];
|
|
258
|
-
|
|
281
|
+
|
|
282
|
+
// 1. main agent 的 originalPrompt 作为首段(per-Agent,identity)
|
|
283
|
+
const isMain = agentId === null || agentId === 'main';
|
|
284
|
+
if (isMain) {
|
|
285
|
+
const originalPrompt = this._getMainAgentOriginalPrompt();
|
|
286
|
+
if (originalPrompt && originalPrompt.trim()) {
|
|
287
|
+
sections.push(originalPrompt.trim());
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 2. 全局注册表的所有 parts
|
|
292
|
+
for (const part of this.listForAgent(agentId)) {
|
|
259
293
|
const content = part.get();
|
|
260
294
|
if (content && content.trim()) sections.push(content.trim());
|
|
261
295
|
}
|
|
@@ -266,15 +300,27 @@ class PromptRegistry extends EventEmitter {
|
|
|
266
300
|
}
|
|
267
301
|
|
|
268
302
|
/**
|
|
269
|
-
*
|
|
303
|
+
* 获取 main agent 的 originalPrompt
|
|
304
|
+
*/
|
|
305
|
+
_getMainAgentOriginalPrompt() {
|
|
306
|
+
const main = this._framework?.getMainAgent?.();
|
|
307
|
+
if (!main) return '';
|
|
308
|
+
// 优先用 _originalPrompt(per-Agent 私有),回退到 getOriginalPrompt()
|
|
309
|
+
if (typeof main.getOriginalPrompt === 'function') {
|
|
310
|
+
return main.getOriginalPrompt() || main._originalPrompt || '';
|
|
311
|
+
}
|
|
312
|
+
return main._originalPrompt || '';
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* build() 的别名
|
|
270
317
|
*/
|
|
271
318
|
preview() {
|
|
272
319
|
return this.build();
|
|
273
320
|
}
|
|
274
321
|
|
|
275
322
|
/**
|
|
276
|
-
*
|
|
277
|
-
* 返回 [{ owner, name, slot, order, length, contentPreview }]
|
|
323
|
+
* 渲染结构化清单(调试用)
|
|
278
324
|
*/
|
|
279
325
|
inspect() {
|
|
280
326
|
return this.list().map((part) => {
|
|
@@ -282,8 +328,8 @@ class PromptRegistry extends EventEmitter {
|
|
|
282
328
|
return {
|
|
283
329
|
owner: part.owner,
|
|
284
330
|
name: part.name,
|
|
285
|
-
|
|
286
|
-
|
|
331
|
+
scope: part.scope,
|
|
332
|
+
priority: part.priority,
|
|
287
333
|
description: part.description,
|
|
288
334
|
hasContent: Boolean(content && content.trim()),
|
|
289
335
|
length: content ? content.length : 0,
|
|
@@ -293,4 +339,4 @@ class PromptRegistry extends EventEmitter {
|
|
|
293
339
|
}
|
|
294
340
|
}
|
|
295
341
|
|
|
296
|
-
module.exports = { PromptRegistry, PromptPart };
|
|
342
|
+
module.exports = { PromptRegistry, PromptPart };
|
package/src/agent/sub.js
CHANGED
|
@@ -27,7 +27,7 @@ class SubAgent extends BaseAgent {
|
|
|
27
27
|
this.baseURL = config.baseURL;
|
|
28
28
|
this.providerOptions = config.providerOptions || {};
|
|
29
29
|
|
|
30
|
-
this.defaulTools = ['ext_skill', 'skill_load', 'ext_call', 'subagent_call'
|
|
30
|
+
this.defaulTools = ['ext_skill', 'skill_load', 'ext_call', 'subagent_call'];
|
|
31
31
|
this.bindTools = {
|
|
32
32
|
read: 'read_file', write: 'write_file', edit: 'modify_file',
|
|
33
33
|
glob: 'read_directory', grep: 'search_file', bash: 'shell_exec',
|
|
@@ -129,10 +129,13 @@ class ChatUIOld {
|
|
|
129
129
|
if (trimmed.startsWith('/') && trimmed.includes(':')) {
|
|
130
130
|
const cmdName = trimmed.slice(1).split(' ')[0];
|
|
131
131
|
const cmdArgs = trimmed.slice(cmdName.length + 2);
|
|
132
|
+
const [skillName, ...rest] = cmdName.split(':');
|
|
133
|
+
const commandName = rest.join(':');
|
|
132
134
|
try {
|
|
135
|
+
// 新格式:plugin = `skill:<name>`, tool = command name
|
|
133
136
|
const result = await this.agent.framework.executeTool('ext_call', {
|
|
134
|
-
plugin:
|
|
135
|
-
tool:
|
|
137
|
+
plugin: `skill:${skillName}`,
|
|
138
|
+
tool: commandName,
|
|
136
139
|
args: { command: cmdArgs }
|
|
137
140
|
});
|
|
138
141
|
if (result.success !== false) {
|
package/src/cli/ui/chat-ui.js
CHANGED
|
@@ -374,10 +374,13 @@ class ChatUI {
|
|
|
374
374
|
if (trimmed.startsWith('/') && cmdName) {
|
|
375
375
|
// 检查 skill 命令 (格式: skillname:cmdname)
|
|
376
376
|
if (cmdName.includes(':')) {
|
|
377
|
+
const [skillName, ...rest] = cmdName.split(':');
|
|
378
|
+
const commandName = rest.join(':');
|
|
377
379
|
try {
|
|
380
|
+
// 新格式:plugin = `skill:<name>`, tool = command name
|
|
378
381
|
const result = await this.agent.framework.executeTool('ext_call', {
|
|
379
|
-
plugin:
|
|
380
|
-
tool:
|
|
382
|
+
plugin: `skill:${skillName}`,
|
|
383
|
+
tool: commandName,
|
|
381
384
|
args: { command: cmdArgs }
|
|
382
385
|
});
|
|
383
386
|
if (result.success !== false) {
|
package/src/common/constants.js
CHANGED
|
@@ -53,6 +53,9 @@ const DEFAULT_MAX_MESSAGES_PER_SESSION = 1000;
|
|
|
53
53
|
/** 保留最近的 N 条消息 */
|
|
54
54
|
const KEEP_RECENT_MESSAGES = 20;
|
|
55
55
|
|
|
56
|
+
/** 压缩消息数量阈值 */
|
|
57
|
+
const COMPRESSION_MESSAGE_THRESHOLD = 200;
|
|
58
|
+
|
|
56
59
|
/** 智能压缩启用 */
|
|
57
60
|
const ENABLE_SMART_COMPRESS = true;
|
|
58
61
|
|
|
@@ -191,6 +194,12 @@ const TOKENS_PER_TOOL = 500;
|
|
|
191
194
|
/** 每消息固定开销 token */
|
|
192
195
|
const TOKENS_PER_MESSAGE_OVERHEAD = 4;
|
|
193
196
|
|
|
197
|
+
/** 简单压缩模式 token 限制比例(50%) */
|
|
198
|
+
const SIMPLE_COMPRESS_TOKEN_RATIO = 0.5;
|
|
199
|
+
|
|
200
|
+
/** 智能压缩模式 token 限制比例(75%) */
|
|
201
|
+
const SMART_COMPRESS_TOKEN_RATIO = 0.75;
|
|
202
|
+
|
|
194
203
|
// ============================================================================
|
|
195
204
|
// 导出
|
|
196
205
|
// ============================================================================
|
|
@@ -211,6 +220,7 @@ module.exports = {
|
|
|
211
220
|
|
|
212
221
|
// Compression
|
|
213
222
|
KEEP_RECENT_MESSAGES,
|
|
223
|
+
COMPRESSION_MESSAGE_THRESHOLD,
|
|
214
224
|
ENABLE_SMART_COMPRESS,
|
|
215
225
|
COMPRESSION_TIMEOUT_MS,
|
|
216
226
|
MAX_TOOL_RESULT_SIZE,
|
|
@@ -244,4 +254,6 @@ module.exports = {
|
|
|
244
254
|
TOKENS_PER_CHAR,
|
|
245
255
|
TOKENS_PER_TOOL,
|
|
246
256
|
TOKENS_PER_MESSAGE_OVERHEAD,
|
|
257
|
+
SIMPLE_COMPRESS_TOKEN_RATIO,
|
|
258
|
+
SMART_COMPRESS_TOKEN_RATIO,
|
|
247
259
|
};
|