foliko 2.0.2 → 2.0.4
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 +1 -0
- package/plugins/core/coordinator/index.js +10 -5
- 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 +52 -38
- 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 +1 -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/chat.js +67 -5
- 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 +20 -25
- 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 +21 -17
- 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 +3 -3
- package/src/context/compressor.js +17 -9
- package/src/framework/framework.js +218 -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/session/chat.js +1 -1
- package/src/session/entry.js +1 -11
- package/src/storage/manager.js +1 -12
- package/src/tool/executor.js +3 -2
- 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
|
@@ -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) {
|
package/src/agent/sub.js
CHANGED
|
@@ -27,10 +27,10 @@ class SubAgent extends BaseAgent {
|
|
|
27
27
|
this.baseURL = config.baseURL;
|
|
28
28
|
this.providerOptions = config.providerOptions || {};
|
|
29
29
|
|
|
30
|
-
this.defaulTools = ['ext_skill', '
|
|
30
|
+
this.defaulTools = ['ext_skill', 'skill_load', 'ext_call', 'subagent_call', 'mcp_call', 'mcp_tool_schema'];
|
|
31
31
|
this.bindTools = {
|
|
32
32
|
read: 'read_file', write: 'write_file', edit: 'modify_file',
|
|
33
|
-
glob: 'read_directory', grep: 'search_file', bash: '
|
|
33
|
+
glob: 'read_directory', grep: 'search_file', bash: 'shell_exec',
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
this._customSystemPrompt = config.systemPrompt || null;
|
|
@@ -39,24 +39,13 @@ class SubAgent extends BaseAgent {
|
|
|
39
39
|
this.maxRetries = config.maxRetries ?? 2;
|
|
40
40
|
this.retryDelay = config.retryDelay ?? 5000;
|
|
41
41
|
this.disableTools = config.disableTools ?? false;
|
|
42
|
-
this.
|
|
42
|
+
this.hidden = config.hidden || false; // 隐藏子Agent,不在指令系统中显示
|
|
43
43
|
this._isSubagent = true;
|
|
44
44
|
|
|
45
45
|
this.providerOptions.deepseek = this.providerOptions.deepseek || {};
|
|
46
46
|
this.providerOptions.deepseek.thinking = { type: 'disabled' };
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
this.framework.once('framework:ready', () => {
|
|
50
|
-
const extExecutor = this.framework?.pluginManager?.get('extension-executor');
|
|
51
|
-
extExecutor?._refreshAllAgentsExtPrompt?.(this.framework);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
registerPromptPart(name, priority = 100, provider) {
|
|
57
|
-
this._promptParts.push({ name, priority, provider });
|
|
58
|
-
this._promptParts.sort((a, b) => a.priority - b.priority);
|
|
59
|
-
return this;
|
|
48
|
+
// 扩展工具 prompt 由 _syncPromptParts 同步到 promptRegistry,无需显式刷新
|
|
60
49
|
}
|
|
61
50
|
|
|
62
51
|
_validateMessagesPairing(messages) { return validateAll(messages); }
|
|
@@ -74,7 +63,15 @@ class SubAgent extends BaseAgent {
|
|
|
74
63
|
|
|
75
64
|
_getAIProvider() {
|
|
76
65
|
const { createAI } = require('../llm/provider');
|
|
77
|
-
|
|
66
|
+
// 优先从 framework 的 ai 插件拿最新配置(避免构造时拿到 stale apiKey)
|
|
67
|
+
const aiPlugin = this.framework?.pluginManager?.get('ai');
|
|
68
|
+
const aiConfig = aiPlugin ? aiPlugin.getConfig() : {};
|
|
69
|
+
return createAI({
|
|
70
|
+
provider: aiConfig.provider || this.provider,
|
|
71
|
+
model: aiConfig.model || this.model,
|
|
72
|
+
apiKey: aiConfig.apiKey || this.apiKey,
|
|
73
|
+
baseURL: aiConfig.baseURL || this.baseURL,
|
|
74
|
+
});
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
_buildAITools() {
|
|
@@ -111,15 +108,13 @@ class SubAgent extends BaseAgent {
|
|
|
111
108
|
const mainPrompt = mainAgent.getOriginalPrompt();
|
|
112
109
|
if (mainPrompt) { lines.push('## 主Agent的系统提示词'); lines.push(mainPrompt); lines.push(''); }
|
|
113
110
|
}
|
|
114
|
-
const subagentManager = this.framework?.pluginManager?.get('subagent-manager');
|
|
115
|
-
if (subagentManager?._buildDescription) lines.push(subagentManager._buildDescription());
|
|
116
111
|
lines.push('');
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
for (const part of this.
|
|
112
|
+
// 渲染通过 promptRegistry 注册的 prompt part(按 slot/order 排序)
|
|
113
|
+
// 扩展工具描述、子Agent 描述等都由 _syncPromptParts 同步到 promptRegistry,
|
|
114
|
+
// 无需再单独硬编码调用 _buildExtensionsDescription / _buildDescription
|
|
115
|
+
for (const part of (this.promptRegistry?.list() || [])) {
|
|
121
116
|
try {
|
|
122
|
-
const content =
|
|
117
|
+
const content = part.get();
|
|
123
118
|
if (content?.trim()) lines.push('', content.trim());
|
|
124
119
|
} catch (err) {
|
|
125
120
|
logger.warn(`[SubAgent:${this.name}] Prompt part '${part.name}' failed:`, err.message);
|
|
@@ -127,7 +122,7 @@ class SubAgent extends BaseAgent {
|
|
|
127
122
|
}
|
|
128
123
|
lines.push('', '## 核心职责', `- **专注本职**:只完成与 ${this.role} 相关的任务`, '- **不越界**:不要代替其他子Agent完成任务', '- **返回结果**:返回简洁明确的操作结果', '');
|
|
129
124
|
lines.push('当你被调用时,你应该:', '1. 仔细理解任务要求', '2. 如果任务不在职责范围内,返回"此任务不在我的职责范围内"', '3. 使用你的工具集完成任务', '4. 返回完整结果');
|
|
130
|
-
lines.push('', '## 禁止事项', '- 处理本职外的任务', '- 代替其他子Agent执行操作', '- 在回复中透露内部实现细节', '- 不先调用 ext_skill/
|
|
125
|
+
lines.push('', '## 禁止事项', '- 处理本职外的任务', '- 代替其他子Agent执行操作', '- 在回复中透露内部实现细节', '- 不先调用 ext_skill/skill_load 就直接使用 ext_call');
|
|
131
126
|
return lines.join('\n');
|
|
132
127
|
}
|
|
133
128
|
|
|
@@ -162,7 +157,7 @@ class SubAgent extends BaseAgent {
|
|
|
162
157
|
} catch (err) {
|
|
163
158
|
lastError = err;
|
|
164
159
|
if (isNetworkError(err) && attempt < maxRetries) {
|
|
165
|
-
logger.warn(`[SubAgent:${this.name}] AI
|
|
160
|
+
logger.warn(`[SubAgent:${this.name}] AI 服务不可用,重试中 (${attempt + 1}/${maxRetries})`);
|
|
166
161
|
await new Promise(resolve => setTimeout(resolve, retryDelay * Math.pow(2, attempt)));
|
|
167
162
|
continue;
|
|
168
163
|
}
|
|
@@ -5,70 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
8
|
const https = require('https');
|
|
10
9
|
const http = require('http');
|
|
11
|
-
const { DEFAULT_REPO, shouldIgnore } = require('
|
|
10
|
+
const { DEFAULT_REPO, shouldIgnore, copyDirRecursive, parseGitUrl, gitCommand } = require('../../utils');
|
|
12
11
|
const { AGENT_DIR_NAME } = require('../utils/config');
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
* 递归复制目录(带过滤)
|
|
16
|
-
*/
|
|
17
|
-
function copyDirRecursive(src, dest, ignorePatterns = []) {
|
|
18
|
-
if (!fs.existsSync(src)) return;
|
|
19
|
-
|
|
20
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
21
|
-
|
|
22
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
23
|
-
|
|
24
|
-
for (const entry of entries) {
|
|
25
|
-
const srcPath = path.join(src, entry.name);
|
|
26
|
-
const destPath = path.join(dest, entry.name);
|
|
27
|
-
|
|
28
|
-
// 检查是否忽略
|
|
29
|
-
if (shouldIgnore(entry.name)) {
|
|
30
|
-
console.log(` Ignoring: ${entry.name}`);
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (entry.isDirectory()) {
|
|
35
|
-
copyDirRecursive(srcPath, destPath, ignorePatterns);
|
|
36
|
-
} else {
|
|
37
|
-
fs.copyFileSync(srcPath, destPath);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 解析 Git URL 获取信息
|
|
44
|
-
*/
|
|
45
|
-
function parseGitUrl(url) {
|
|
46
|
-
// 支持多种格式
|
|
47
|
-
const patterns = [
|
|
48
|
-
/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
49
|
-
/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
for (const pattern of patterns) {
|
|
53
|
-
const match = url.match(pattern);
|
|
54
|
-
if (match) {
|
|
55
|
-
return { owner: match[1], repo: match[2] };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 执行 Git 命令
|
|
63
|
-
*/
|
|
64
|
-
function gitCommand(args, cwd) {
|
|
65
|
-
try {
|
|
66
|
-
return execSync(`git ${args}`, { cwd, encoding: 'utf-8', stdio: 'pipe' });
|
|
67
|
-
} catch (err) {
|
|
68
|
-
return err.stdout || err.stderr || '';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
13
|
/**
|
|
73
14
|
* 下载文件
|
|
74
15
|
*/
|
|
@@ -168,7 +168,7 @@ class ChatUIOld {
|
|
|
168
168
|
|
|
169
169
|
async _reloadSkills() {
|
|
170
170
|
try {
|
|
171
|
-
const result = await this.agent.framework.executeTool('
|
|
171
|
+
const result = await this.agent.framework.executeTool('skill_reload', {});
|
|
172
172
|
if (result.success !== false) {
|
|
173
173
|
console.log(colored('[提示] 技能已重载', CYAN));
|
|
174
174
|
} else {
|
package/src/cli/ui/chat-ui.js
CHANGED
|
@@ -287,9 +287,11 @@ class ChatUI {
|
|
|
287
287
|
const self=this;
|
|
288
288
|
queue.add(function(){
|
|
289
289
|
var next = this.next;
|
|
290
|
+
self.interrupted = false; // 新消息重置中断状态,确保 stream chunks 正常处理
|
|
290
291
|
self.agent.sendMessage(message, { sessionId:self.sessionId }).then(next).catch(function(e){
|
|
291
|
-
|
|
292
|
+
// 错误由 sessionScope.on('queue:failed') 统一显示,此处只需清理状态
|
|
292
293
|
self.clear_message_done();
|
|
294
|
+
next(); // 必须调 next() 推进队列,否则后续消息卡死
|
|
293
295
|
});
|
|
294
296
|
})
|
|
295
297
|
|
|
@@ -321,7 +323,7 @@ class ChatUI {
|
|
|
321
323
|
|
|
322
324
|
if (cmd === '/reload') {
|
|
323
325
|
try {
|
|
324
|
-
const result = await this.agent.framework.executeTool('
|
|
326
|
+
const result = await this.agent.framework.executeTool('skill_reload', {});
|
|
325
327
|
if (result.success !== false) {
|
|
326
328
|
this.create_message(`${colored('[提示]', CYAN)} 技能已重载\n`,chalk.dim('● '))
|
|
327
329
|
} else {
|
|
@@ -592,11 +594,11 @@ class ChatUI {
|
|
|
592
594
|
}
|
|
593
595
|
} else if (chunk.type === 'tool-call') {
|
|
594
596
|
const args = chunk.input ? JSON.stringify(chunk.input).slice(0, 30) : '';
|
|
595
|
-
this.statusBar.tooler.show(`${
|
|
597
|
+
this.statusBar.tooler.show(`${folikoGold(chunk.toolName.toLocaleUpperCase())} ${chalk.gray(args+'...')}`)
|
|
596
598
|
} else if(chunk.type==='tool-result'){
|
|
597
599
|
const result = chunk.result;
|
|
598
600
|
const shortResult = typeof result === 'string' ? result.slice(0, 30) : JSON.stringify(result).slice(0, 30);
|
|
599
|
-
this.statusBar.tooler.show(`${
|
|
601
|
+
this.statusBar.tooler.show(`${folikoGold(chunk.toolName.toLocaleUpperCase())} ${chalk.gray(shortResult+'...')}`)
|
|
600
602
|
|
|
601
603
|
// 如果是 edit/edit_file 的结果并且包含 diff,渲染彩色 diff
|
|
602
604
|
if (chunk.toolName === 'edit' || chunk.toolName === 'edit_file') {
|
|
@@ -610,7 +612,7 @@ class ChatUI {
|
|
|
610
612
|
const bgColor = chalk.bgHex('#1a1a2e');
|
|
611
613
|
this._currentBotMessage.appendContent(['\n',diffContent,'\n\n'].join(''));
|
|
612
614
|
// this.create_message(diffContent, chalk.yellow('▸ '), true, (line) => bgColor(line));
|
|
613
|
-
this.statusBar.tooler.show(`${chalk.green('[
|
|
615
|
+
this.statusBar.tooler.show(`${chalk.green('[差异]')} ${folikoGold(resultObj.filePath)}`);
|
|
614
616
|
}
|
|
615
617
|
}
|
|
616
618
|
// setTimeout(() => this.tooler.hide(), 3000);
|
|
@@ -662,23 +664,25 @@ class ChatUI {
|
|
|
662
664
|
|
|
663
665
|
this.interrupted = false;
|
|
664
666
|
|
|
665
|
-
const interruptHandler = () => {
|
|
666
|
-
if (!this.interrupted) {
|
|
667
|
-
this.interrupted = true;
|
|
668
|
-
console.log(`\n${colored('[中断]', RED)} 输出已中断`);
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
|
|
673
667
|
this.tui.addInputListener((data) => {
|
|
674
668
|
if (matchesKey(data, 'ctrl+c')) {
|
|
675
|
-
this.
|
|
676
|
-
|
|
669
|
+
if (this.isResponding) {
|
|
670
|
+
// 正在响应中:中断对话但不退出
|
|
671
|
+
this.isResponding = false;
|
|
672
|
+
this.interrupted = true;
|
|
673
|
+
this.agent?.framework?.stop();
|
|
674
|
+
} else {
|
|
675
|
+
// 空闲状态:退出
|
|
676
|
+
this.tui.stop();
|
|
677
|
+
process.exit(0);
|
|
678
|
+
}
|
|
677
679
|
}
|
|
678
680
|
});
|
|
679
681
|
this.tui.start();
|
|
680
|
-
|
|
681
|
-
|
|
682
|
+
|
|
683
|
+
process.on('SIGINT', () => {
|
|
684
|
+
// 防止默认 SIGINT 退出(由 tui 的 ctrl+c 处理)
|
|
685
|
+
});
|
|
682
686
|
}
|
|
683
687
|
|
|
684
688
|
|
|
@@ -78,7 +78,7 @@ class AgentMentionProvider {
|
|
|
78
78
|
|
|
79
79
|
const mainAgent = this.framework._mainAgent;
|
|
80
80
|
return this.framework._agents
|
|
81
|
-
.filter(agent => agent !== mainAgent && agent.name)
|
|
81
|
+
.filter(agent => agent !== mainAgent && agent.name && !agent.hidden)
|
|
82
82
|
.map(agent => ({
|
|
83
83
|
name: agent.name.replace(/^subagent_/, '').replace(/^session_/, ''),
|
|
84
84
|
description: agent._role || agent.description || agent.name,
|