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/plugin/manager.js
CHANGED
|
@@ -42,6 +42,15 @@ class PluginManager {
|
|
|
42
42
|
* 注册插件(不加载,只记录状态)
|
|
43
43
|
*/
|
|
44
44
|
register(plugin, options = {}) {
|
|
45
|
+
// 处理 function(Plugin) { return class... } 格式
|
|
46
|
+
if (typeof plugin === 'function' && !(plugin instanceof Plugin)) {
|
|
47
|
+
try {
|
|
48
|
+
plugin = plugin(Plugin);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new PluginError(`Plugin factory function failed: ${err.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
if (!(plugin instanceof Plugin)) {
|
|
46
55
|
throw new PluginError('Plugin must be an instance of Plugin');
|
|
47
56
|
}
|
|
@@ -146,9 +155,18 @@ class PluginManager {
|
|
|
146
155
|
async load(plugin, options = {}) {
|
|
147
156
|
let pluginInstance = plugin;
|
|
148
157
|
|
|
149
|
-
//
|
|
150
|
-
if (typeof plugin === 'function'
|
|
151
|
-
|
|
158
|
+
// 处理 function(Plugin) { return class... } 格式
|
|
159
|
+
if (typeof plugin === 'function') {
|
|
160
|
+
if (plugin.prototype instanceof Plugin) {
|
|
161
|
+
// Plugin 子类
|
|
162
|
+
pluginInstance = new plugin();
|
|
163
|
+
} else {
|
|
164
|
+
// Factory function
|
|
165
|
+
pluginInstance = plugin(Plugin);
|
|
166
|
+
if (pluginInstance.prototype instanceof Plugin) {
|
|
167
|
+
pluginInstance = new pluginInstance();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
152
170
|
}
|
|
153
171
|
|
|
154
172
|
const name = pluginInstance.name;
|
|
@@ -187,7 +205,7 @@ class PluginManager {
|
|
|
187
205
|
this._log.info(`Loading plugin: ${name}`);
|
|
188
206
|
|
|
189
207
|
if (typeof pluginInstance.install === 'function') {
|
|
190
|
-
pluginInstance.install(this.framework);
|
|
208
|
+
await pluginInstance.install(this.framework);
|
|
191
209
|
}
|
|
192
210
|
|
|
193
211
|
entry.status = 'loaded';
|
|
@@ -250,10 +268,10 @@ class PluginManager {
|
|
|
250
268
|
await plugin.reload(this.framework);
|
|
251
269
|
} else {
|
|
252
270
|
if (typeof plugin.uninstall === 'function') {
|
|
253
|
-
plugin.uninstall(this.framework);
|
|
271
|
+
await plugin.uninstall(this.framework);
|
|
254
272
|
}
|
|
255
273
|
if (typeof plugin.install === 'function') {
|
|
256
|
-
plugin.install(this.framework);
|
|
274
|
+
await plugin.install(this.framework);
|
|
257
275
|
}
|
|
258
276
|
if (typeof plugin.start === 'function') {
|
|
259
277
|
await plugin.start(this.framework);
|
|
@@ -342,10 +360,10 @@ class PluginManager {
|
|
|
342
360
|
} else {
|
|
343
361
|
// 没有 reload,走完整 uninstall + install + start
|
|
344
362
|
if (typeof entry.instance.uninstall === 'function') {
|
|
345
|
-
entry.instance.uninstall(this.framework);
|
|
363
|
+
await entry.instance.uninstall(this.framework);
|
|
346
364
|
}
|
|
347
365
|
if (typeof entry.instance.install === 'function') {
|
|
348
|
-
entry.instance.install(this.framework);
|
|
366
|
+
await entry.instance.install(this.framework);
|
|
349
367
|
}
|
|
350
368
|
if (typeof entry.instance.start === 'function') {
|
|
351
369
|
await entry.instance.start(this.framework);
|
|
@@ -405,6 +423,9 @@ class PluginManager {
|
|
|
405
423
|
}
|
|
406
424
|
}
|
|
407
425
|
|
|
426
|
+
// 清理该插件注册的提示词
|
|
427
|
+
entry.instance._cleanupPrompts?.();
|
|
428
|
+
|
|
408
429
|
this.framework?.emit('plugin:disabled', entry.instance);
|
|
409
430
|
this._saveState();
|
|
410
431
|
this._log.info(`Plugin '${name}' disabled`);
|
package/src/tool/schema.js
CHANGED
|
@@ -13,15 +13,36 @@ const { z } = require('zod');
|
|
|
13
13
|
* @param {boolean} [required] - Whether this schema is required (optional when false)
|
|
14
14
|
* @returns {z.ZodType}
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* Convert JSON Schema to Zod schema
|
|
18
|
+
* @param {Object} jsonSchema
|
|
19
|
+
* @param {boolean} [required] - Whether the root schema is required (default true)
|
|
20
|
+
* @returns {z.ZodType}
|
|
21
|
+
*/
|
|
16
22
|
function jsonSchemaToZod(jsonSchema, required) {
|
|
17
23
|
if (!jsonSchema || typeof jsonSchema !== 'object') {
|
|
18
24
|
return z.any();
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
// 根级默认 required=true,optional() 在对象字段级别应用
|
|
28
|
+
const isRequired = required !== false;
|
|
29
|
+
return _applyOptional(_convertSchema(jsonSchema, true), isRequired, jsonSchema);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 在对象字段构建时调用:根据 required 包装
|
|
34
|
+
*/
|
|
35
|
+
function _applyOptional(zodType, isRequired, schema) {
|
|
36
|
+
if (isRequired) return zodType;
|
|
37
|
+
if (schema && schema.default !== undefined) return zodType;
|
|
38
|
+
return zodType.optional();
|
|
22
39
|
}
|
|
23
40
|
|
|
24
|
-
|
|
41
|
+
/**
|
|
42
|
+
* 内部转换:只负责类型转换,不做 optional 包装
|
|
43
|
+
* optional 包装由调用方根据 required 参数决定
|
|
44
|
+
*/
|
|
45
|
+
function _convertSchema(schema, _required = true) {
|
|
25
46
|
if (!schema) return z.any();
|
|
26
47
|
|
|
27
48
|
let type = schema.type;
|
|
@@ -55,8 +76,10 @@ function _convertSchema(schema, required = false) {
|
|
|
55
76
|
zodType = z.boolean();
|
|
56
77
|
break;
|
|
57
78
|
case 'array': {
|
|
79
|
+
// 数组项的 required 由 _required 决定(通常为 true,因为数组存在即代表"有数组")
|
|
80
|
+
const itemRequired = _required && schema.items?.required !== false;
|
|
58
81
|
if (schema.items) {
|
|
59
|
-
zodType = z.array(_convertSchema(schema.items,
|
|
82
|
+
zodType = z.array(_convertSchema(schema.items, itemRequired));
|
|
60
83
|
} else {
|
|
61
84
|
zodType = z.array(z.any());
|
|
62
85
|
}
|
|
@@ -68,12 +91,14 @@ function _convertSchema(schema, required = false) {
|
|
|
68
91
|
break;
|
|
69
92
|
}
|
|
70
93
|
const shape = {};
|
|
71
|
-
const req = schema.required || [];
|
|
94
|
+
const req = new Set(schema.required || []);
|
|
72
95
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
73
|
-
|
|
96
|
+
const isRequired = req.has(key);
|
|
97
|
+
// 子字段的 optional() 在这里统一包装
|
|
98
|
+
const fieldSchema = _convertSchema(prop, isRequired);
|
|
99
|
+
shape[key] = _applyOptional(fieldSchema, isRequired, prop);
|
|
74
100
|
}
|
|
75
|
-
|
|
76
|
-
zodType = req.length === Object.keys(shape).length ? obj : obj.partial();
|
|
101
|
+
zodType = z.object(shape);
|
|
77
102
|
break;
|
|
78
103
|
}
|
|
79
104
|
case 'null':
|
|
@@ -84,8 +109,6 @@ function _convertSchema(schema, required = false) {
|
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
if (schema.nullable) zodType = zodType.nullable();
|
|
87
|
-
if (!required && schema.default === undefined) zodType = zodType.optional();
|
|
88
|
-
|
|
89
112
|
return zodType;
|
|
90
113
|
}
|
|
91
114
|
|
|
@@ -17,7 +17,7 @@ describe('Plugin declarative prompts', () => {
|
|
|
17
17
|
prompts = [
|
|
18
18
|
{
|
|
19
19
|
name: 'demo',
|
|
20
|
-
|
|
20
|
+
priority: 50,
|
|
21
21
|
description: 'demo part',
|
|
22
22
|
provider: function () {
|
|
23
23
|
return 'demo-content';
|
|
@@ -43,7 +43,7 @@ describe('Plugin declarative prompts', () => {
|
|
|
43
43
|
prompts = [
|
|
44
44
|
{
|
|
45
45
|
name: 'foo',
|
|
46
|
-
|
|
46
|
+
scope: 'global',
|
|
47
47
|
provider: function () {
|
|
48
48
|
return 'foo';
|
|
49
49
|
},
|
|
@@ -70,7 +70,7 @@ describe('Plugin declarative prompts', () => {
|
|
|
70
70
|
{ provider: () => 'no-name' },
|
|
71
71
|
{
|
|
72
72
|
name: 'valid',
|
|
73
|
-
|
|
73
|
+
priority: 50,
|
|
74
74
|
provider: function () {
|
|
75
75
|
return 'valid-content';
|
|
76
76
|
},
|
|
@@ -99,7 +99,7 @@ describe('Plugin declarative prompts', () => {
|
|
|
99
99
|
prompts = [
|
|
100
100
|
{
|
|
101
101
|
name: 'capture',
|
|
102
|
-
|
|
102
|
+
scope: 'global',
|
|
103
103
|
provider: function () {
|
|
104
104
|
capturedThis = this;
|
|
105
105
|
return 'x';
|
|
@@ -122,8 +122,8 @@ describe('Plugin declarative prompts', () => {
|
|
|
122
122
|
return this;
|
|
123
123
|
}
|
|
124
124
|
prompts = [
|
|
125
|
-
{ name: 'a',
|
|
126
|
-
{ name: 'b',
|
|
125
|
+
{ name: 'a', priority: 50, provider: () => 'a' },
|
|
126
|
+
{ name: 'b', priority: 60, provider: () => 'b' },
|
|
127
127
|
];
|
|
128
128
|
}
|
|
129
129
|
const plugin = new CleanupPlugin();
|
|
@@ -146,7 +146,7 @@ describe('Plugin declarative prompts', () => {
|
|
|
146
146
|
prompts = [
|
|
147
147
|
{
|
|
148
148
|
name: 'reloadable',
|
|
149
|
-
|
|
149
|
+
priority: 50,
|
|
150
150
|
provider: function () {
|
|
151
151
|
return 'v1';
|
|
152
152
|
},
|
|
@@ -177,7 +177,7 @@ describe('Plugin declarative prompts', () => {
|
|
|
177
177
|
prompts = [
|
|
178
178
|
{
|
|
179
179
|
name: 'demo',
|
|
180
|
-
|
|
180
|
+
priority: 50,
|
|
181
181
|
provider: function () {
|
|
182
182
|
return 'demo-content';
|
|
183
183
|
},
|
|
@@ -192,7 +192,7 @@ describe('Plugin declarative prompts', () => {
|
|
|
192
192
|
expect(fw.prompts.getPart('test-owner', 'demo').get()).toBe('demo-content');
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
it('renders in
|
|
195
|
+
it('renders in priority order', () => {
|
|
196
196
|
class MultiPlugin extends Plugin {
|
|
197
197
|
name = 'multi';
|
|
198
198
|
install(f) {
|
|
@@ -200,9 +200,9 @@ describe('Plugin declarative prompts', () => {
|
|
|
200
200
|
return this;
|
|
201
201
|
}
|
|
202
202
|
prompts = [
|
|
203
|
-
{ name: 'behavior',
|
|
204
|
-
{ name: 'identity',
|
|
205
|
-
{ name: 'capability',
|
|
203
|
+
{ name: 'behavior', priority: 50, provider: () => 'BEHAVIOR_CONTENT' },
|
|
204
|
+
{ name: 'identity', priority: 10, provider: () => 'IDENTITY_CONTENT' },
|
|
205
|
+
{ name: 'capability', priority: 30, provider: () => 'CAPABILITY_CONTENT' },
|
|
206
206
|
];
|
|
207
207
|
}
|
|
208
208
|
const plugin = new MultiPlugin();
|
|
@@ -216,4 +216,4 @@ describe('Plugin declarative prompts', () => {
|
|
|
216
216
|
expect(identityIdx).toBeLessThan(capabilityIdx);
|
|
217
217
|
expect(capabilityIdx).toBeLessThan(behaviorIdx);
|
|
218
218
|
});
|
|
219
|
-
});
|
|
219
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { PromptRegistry
|
|
2
|
+
import { PromptRegistry } from '../../src/index';
|
|
3
3
|
|
|
4
4
|
describe('PromptRegistry', () => {
|
|
5
5
|
let reg;
|
|
@@ -10,13 +10,20 @@ describe('PromptRegistry', () => {
|
|
|
10
10
|
|
|
11
11
|
describe('basic API', () => {
|
|
12
12
|
it('registers and retrieves a part', () => {
|
|
13
|
-
reg.register('plugin-a', 'foo', () => 'hello'
|
|
13
|
+
reg.register('plugin-a', 'foo', () => 'hello');
|
|
14
14
|
const part = reg.getPart('plugin-a', 'foo');
|
|
15
15
|
expect(part).toBeTruthy();
|
|
16
16
|
expect(part.get()).toBe('hello');
|
|
17
17
|
expect(reg.count()).toBe(1);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
it('registers with scope and priority', () => {
|
|
21
|
+
reg.register('plugin-a', 'foo', () => 'hello', { scope: 'global', priority: 50 });
|
|
22
|
+
const part = reg.getPart('plugin-a', 'foo');
|
|
23
|
+
expect(part.scope).toBe('global');
|
|
24
|
+
expect(part.priority).toBe(50);
|
|
25
|
+
});
|
|
26
|
+
|
|
20
27
|
it('requires owner/name/provider', () => {
|
|
21
28
|
expect(() => reg.register('', 'foo', () => 'x')).toThrow(/owner/);
|
|
22
29
|
expect(() => reg.register('p', '', () => 'x')).toThrow(/name/);
|
|
@@ -47,46 +54,53 @@ describe('PromptRegistry', () => {
|
|
|
47
54
|
});
|
|
48
55
|
});
|
|
49
56
|
|
|
50
|
-
describe('
|
|
51
|
-
it('orders by
|
|
52
|
-
reg.register('p', 'a', () => '
|
|
53
|
-
reg.register('p', 'b', () => '
|
|
54
|
-
reg.register('p', 'c', () => '
|
|
55
|
-
const order = reg.list().map((p) => p.
|
|
56
|
-
expect(order).toEqual(['
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('uses explicit order within same slot', () => {
|
|
60
|
-
reg.register('p', 'late', () => 'late', { slot: 'IDENTITY', order: 5 });
|
|
61
|
-
reg.register('p', 'early', () => 'early', { slot: 'IDENTITY', order: 1 });
|
|
62
|
-
const names = reg.list().map((p) => p.name);
|
|
63
|
-
expect(names).toEqual(['early', 'late']);
|
|
57
|
+
describe('priority ordering', () => {
|
|
58
|
+
it('orders by priority (smaller first)', () => {
|
|
59
|
+
reg.register('p', 'a', () => 'a', { priority: 100 });
|
|
60
|
+
reg.register('p', 'b', () => 'b', { priority: 10 });
|
|
61
|
+
reg.register('p', 'c', () => 'c', { priority: 50 });
|
|
62
|
+
const order = reg.list().map((p) => p.name);
|
|
63
|
+
expect(order).toEqual(['b', 'c', 'a']);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it('falls back to alphabetical when
|
|
67
|
-
reg.register('zeta', 'z', () => 'z', {
|
|
68
|
-
reg.register('alpha', 'a', () => 'a', {
|
|
66
|
+
it('falls back to owner alphabetical when priority is equal', () => {
|
|
67
|
+
reg.register('zeta', 'z', () => 'z', { priority: 50 });
|
|
68
|
+
reg.register('alpha', 'a', () => 'a', { priority: 50 });
|
|
69
69
|
const owners = reg.list().map((p) => p.owner);
|
|
70
70
|
expect(owners).toEqual(['alpha', 'zeta']);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
it('
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
expect(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
it('defaults to priority 100', () => {
|
|
74
|
+
reg.register('p', 'a', () => 'a');
|
|
75
|
+
reg.register('p', 'b', () => 'b', { priority: 50 });
|
|
76
|
+
const order = reg.list().map((p) => p.name);
|
|
77
|
+
expect(order).toEqual(['b', 'a']);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('scope filtering', () => {
|
|
82
|
+
it('listByScope filters correctly', () => {
|
|
83
|
+
reg.register('p', 'a', () => 'a', { scope: 'global' });
|
|
84
|
+
reg.register('p', 'b', () => 'b', { scope: 'main' });
|
|
85
|
+
reg.register('p', 'c', () => 'c', { scope: 'sub-1' });
|
|
86
|
+
expect(reg.listByScope('global').map((p) => p.name)).toEqual(['a']);
|
|
87
|
+
expect(reg.listByScope('main').map((p) => p.name)).toEqual(['b']);
|
|
88
|
+
expect(reg.listByScope('sub-1').map((p) => p.name)).toEqual(['c']);
|
|
82
89
|
});
|
|
83
90
|
|
|
84
|
-
it('
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
it('listForAgent includes global and matching scope', () => {
|
|
92
|
+
reg.register('p', 'global', () => 'g', { scope: 'global' });
|
|
93
|
+
reg.register('p', 'main', () => 'm', { scope: 'main' });
|
|
94
|
+
reg.register('p', 'sub1', () => 's1', { scope: 'sub-1' });
|
|
95
|
+
reg.register('p', 'sub2', () => 's2', { scope: 'sub-2' });
|
|
96
|
+
|
|
97
|
+
// Main agent sees global + main
|
|
98
|
+
const mainParts = reg.listForAgent(null).map((p) => p.name);
|
|
99
|
+
expect(mainParts).toEqual(['global', 'main']);
|
|
100
|
+
|
|
101
|
+
// sub-1 agent sees global + sub1
|
|
102
|
+
const sub1Parts = reg.listForAgent('sub-1').map((p) => p.name);
|
|
103
|
+
expect(sub1Parts).toEqual(['global', 'sub1']);
|
|
90
104
|
});
|
|
91
105
|
});
|
|
92
106
|
|
|
@@ -97,6 +111,13 @@ describe('PromptRegistry', () => {
|
|
|
97
111
|
expect(reg.build()).toBe('AAA\n\nBBB');
|
|
98
112
|
});
|
|
99
113
|
|
|
114
|
+
it('builds only parts for specific agent', () => {
|
|
115
|
+
reg.register('p', 'global', () => 'G', { scope: 'global' });
|
|
116
|
+
reg.register('p', 'main', () => 'M', { scope: 'main' });
|
|
117
|
+
reg.register('p', 'sub1', () => 'S1', { scope: 'sub-1' });
|
|
118
|
+
expect(reg.buildForAgent('sub-1')).toBe('G\n\nS1');
|
|
119
|
+
});
|
|
120
|
+
|
|
100
121
|
it('skips null/empty content', () => {
|
|
101
122
|
reg.register('p', 'a', () => 'AAA');
|
|
102
123
|
reg.register('p', 'b', () => null);
|
|
@@ -116,7 +137,6 @@ describe('PromptRegistry', () => {
|
|
|
116
137
|
console.warn = (msg) => warnings.push(msg);
|
|
117
138
|
try {
|
|
118
139
|
const result = reg.build();
|
|
119
|
-
// 排序:after → bad(fail) → good → "BBB\n\nAAA"
|
|
120
140
|
expect(result).toBe('BBB\n\nAAA');
|
|
121
141
|
expect(warnings.length).toBe(1);
|
|
122
142
|
} finally {
|
|
@@ -170,14 +190,14 @@ describe('PromptRegistry', () => {
|
|
|
170
190
|
|
|
171
191
|
describe('inspect', () => {
|
|
172
192
|
it('returns structured per-part metadata', () => {
|
|
173
|
-
reg.register('plugin-a', 'foo', () => 'hello world', {
|
|
193
|
+
reg.register('plugin-a', 'foo', () => 'hello world', { scope: 'global', priority: 50 });
|
|
174
194
|
const out = reg.inspect();
|
|
175
195
|
expect(out).toHaveLength(1);
|
|
176
196
|
expect(out[0]).toMatchObject({
|
|
177
197
|
owner: 'plugin-a',
|
|
178
198
|
name: 'foo',
|
|
179
|
-
|
|
180
|
-
|
|
199
|
+
scope: 'global',
|
|
200
|
+
priority: 50,
|
|
181
201
|
hasContent: true,
|
|
182
202
|
length: 11,
|
|
183
203
|
});
|
|
@@ -190,20 +210,8 @@ describe('PromptRegistry', () => {
|
|
|
190
210
|
const { Framework } = require('../../src/index');
|
|
191
211
|
const fw = new Framework({ silent: true });
|
|
192
212
|
expect(fw.prompts).toBeDefined();
|
|
193
|
-
expect(fw.prompts).toBe(fw.promptRegistry);
|
|
194
213
|
expect(typeof fw.prompts.register).toBe('function');
|
|
195
214
|
expect(typeof fw.prompts.preview).toBe('function');
|
|
196
215
|
});
|
|
197
|
-
|
|
198
|
-
it('each agent has its own promptRegistry via BaseAgent', () => {
|
|
199
|
-
const { BaseAgent } = require('../../src/agent/base');
|
|
200
|
-
const { Framework } = require('../../src/index');
|
|
201
|
-
const fw = new Framework({ silent: true });
|
|
202
|
-
const agent = new BaseAgent(fw, { name: 'Test' });
|
|
203
|
-
expect(agent.promptRegistry).toBeDefined();
|
|
204
|
-
expect(agent.promptRegistry).toBe(agent._promptRegistry);
|
|
205
|
-
agent.promptRegistry.register('test', 'foo', () => 'x', { slot: 'CUSTOM' });
|
|
206
|
-
expect(agent.promptRegistry.count()).toBe(1);
|
|
207
|
-
});
|
|
208
216
|
});
|
|
209
|
-
});
|
|
217
|
+
});
|