foliko 2.0.5 → 2.0.7
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 +280 -24
- 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 +44 -19
- package/plugins/core/python-loader/index.js +43 -25
- package/plugins/core/scheduler/index.js +1 -0
- package/plugins/core/skill-manager/PROMPT.md +6 -0
- package/plugins/core/skill-manager/index.js +402 -115
- 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/context.js +941 -0
- package/plugins/core/workflow/engine.js +66 -0
- package/plugins/core/workflow/examples/01-basic.js +42 -0
- package/plugins/core/workflow/examples/01-basic.json +30 -0
- package/plugins/core/workflow/examples/02-choice.js +75 -0
- package/plugins/core/workflow/examples/02-choice.json +59 -0
- package/plugins/core/workflow/examples/03-chain-style.js +114 -0
- package/plugins/core/workflow/examples/03-each.json +41 -0
- package/plugins/core/workflow/examples/04-parallel.js +52 -0
- package/plugins/core/workflow/examples/04-parallel.json +51 -0
- package/plugins/core/workflow/examples/05-chain-style.json +68 -0
- package/plugins/core/workflow/examples/05-each.js +65 -0
- package/plugins/core/workflow/examples/06-script.js +82 -0
- package/plugins/core/workflow/examples/07-module-export.js +43 -0
- package/plugins/core/workflow/examples/08-default-export.js +29 -0
- package/plugins/core/workflow/examples/09-next-with-args.js +50 -0
- package/plugins/core/workflow/examples/10-logger.js +34 -0
- package/plugins/core/workflow/examples/11-storage.js +68 -0
- package/plugins/core/workflow/examples/simple.js +77 -0
- package/plugins/core/workflow/examples/simple.json +75 -0
- package/plugins/core/workflow/index.js +204 -78
- package/plugins/core/workflow/js-runner.js +318 -0
- package/plugins/core/workflow/json-runner.js +323 -0
- package/plugins/core/workflow/stages/action.js +211 -0
- package/plugins/core/workflow/stages/choice.js +74 -0
- package/plugins/core/workflow/stages/delay.js +73 -0
- package/plugins/core/workflow/stages/each.js +123 -0
- package/plugins/core/workflow/stages/parallel.js +69 -0
- package/plugins/core/workflow/stages/try.js +142 -0
- package/plugins/executors/data-splitter/PROMPT.md +13 -0
- package/plugins/executors/data-splitter/index.js +8 -6
- 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/sandbox/check-context.js +5 -0
- package/sandbox/test-context.js +27 -0
- package/sandbox/test-fixes.js +40 -0
- package/sandbox/test-hello-js.js +46 -0
- 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 +753 -436
- package/src/agent/chat.js +56 -28
- package/src/agent/main.js +39 -14
- package/src/agent/prompt-registry.js +56 -16
- 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 +9 -5
- package/src/common/constants.js +12 -0
- package/src/common/error-capture.js +91 -0
- package/src/common/json-safe.js +20 -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 +78 -12
- package/src/index.js +4 -0
- package/src/plugin/base.js +908 -5
- package/src/plugin/manager.js +124 -9
- package/src/tool/schema.js +32 -9
- package/src/utils/sandbox.js +1 -1
- package/website/index.html +821 -0
- package/skills/workflows/workflow-troubleshooting/DEBUGGING.md +0 -197
- package/skills/workflows/workflow-troubleshooting/SKILL.md +0 -391
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
|
}
|
|
@@ -69,7 +78,7 @@ class PluginManager {
|
|
|
69
78
|
instance: plugin,
|
|
70
79
|
status: 'registered',
|
|
71
80
|
enabled,
|
|
72
|
-
sourcePath: plugin.sourcePath
|
|
81
|
+
sourcePath: this._detectPluginSourcePath(plugin, plugin.sourcePath),
|
|
73
82
|
});
|
|
74
83
|
this._knownPlugins.add(plugin.name);
|
|
75
84
|
this._log.debug(`Plugin registered: ${plugin.name} (enabled: ${enabled})`);
|
|
@@ -80,6 +89,71 @@ class PluginManager {
|
|
|
80
89
|
return this._plugins.has(name);
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Find the source file path for a plugin instance by scanning plugin
|
|
94
|
+
* class names in candidate directories (used as a fallback when the
|
|
95
|
+
* plugin was created directly via `new SomePlugin(...)` instead of going
|
|
96
|
+
* through `loadPluginFromPath`).
|
|
97
|
+
*/
|
|
98
|
+
_detectPluginSourcePath(plugin, declared) {
|
|
99
|
+
if (declared) return declared;
|
|
100
|
+
|
|
101
|
+
// For system plugins registered in plugins/core/*, the class name often
|
|
102
|
+
// matches the on-disk filename (e.g. `WorkflowPlugin` => `index.js` of
|
|
103
|
+
// `plugins/core/workflow`, `SubAgentManagerPlugin` => `.../sub-agent/index.js`).
|
|
104
|
+
const candidates = [];
|
|
105
|
+
const fs = require('fs');
|
|
106
|
+
const path = require('path');
|
|
107
|
+
try {
|
|
108
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
109
|
+
candidates.push(path.resolve(projectRoot, 'plugins'));
|
|
110
|
+
} catch (_) {}
|
|
111
|
+
|
|
112
|
+
const className =
|
|
113
|
+
(plugin.constructor && plugin.constructor.name) || plugin.name;
|
|
114
|
+
if (!className) return null;
|
|
115
|
+
|
|
116
|
+
for (const base of candidates) {
|
|
117
|
+
// exact-match: <base>/<dir>/index.js with class declared inside
|
|
118
|
+
// heuristic: walk top-level plugin dirs and look for index.js exports
|
|
119
|
+
// a class named `className`.
|
|
120
|
+
let dirs = [];
|
|
121
|
+
try {
|
|
122
|
+
dirs = fs.readdirSync(base);
|
|
123
|
+
} catch (_) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const sub of dirs) {
|
|
127
|
+
const subDir = path.join(base, sub);
|
|
128
|
+
let files = [];
|
|
129
|
+
try {
|
|
130
|
+
files = fs.statSync(subDir).isDirectory()
|
|
131
|
+
? fs.readdirSync(subDir)
|
|
132
|
+
: [path.basename(subDir)];
|
|
133
|
+
subDir = fs.statSync(subDir).isDirectory()
|
|
134
|
+
? subDir
|
|
135
|
+
: path.dirname(subDir);
|
|
136
|
+
} catch (_) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
if (!file.endsWith('.js')) continue;
|
|
141
|
+
const full = path.join(subDir, file);
|
|
142
|
+
try {
|
|
143
|
+
const src = fs.readFileSync(full, 'utf-8');
|
|
144
|
+
// Heuristic class declaration
|
|
145
|
+
if (
|
|
146
|
+
new RegExp(`class\\s+${className}\\s+extends\\s+Plugin`).test(src)
|
|
147
|
+
) {
|
|
148
|
+
return full;
|
|
149
|
+
}
|
|
150
|
+
} catch (_) {}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
83
157
|
get(name) {
|
|
84
158
|
const entry = this._plugins.get(name);
|
|
85
159
|
return entry ? entry.instance : null;
|
|
@@ -146,9 +220,18 @@ class PluginManager {
|
|
|
146
220
|
async load(plugin, options = {}) {
|
|
147
221
|
let pluginInstance = plugin;
|
|
148
222
|
|
|
149
|
-
//
|
|
150
|
-
if (typeof plugin === 'function'
|
|
151
|
-
|
|
223
|
+
// 处理 function(Plugin) { return class... } 格式
|
|
224
|
+
if (typeof plugin === 'function') {
|
|
225
|
+
if (plugin.prototype instanceof Plugin) {
|
|
226
|
+
// Plugin 子类
|
|
227
|
+
pluginInstance = new plugin();
|
|
228
|
+
} else {
|
|
229
|
+
// Factory function
|
|
230
|
+
pluginInstance = plugin(Plugin);
|
|
231
|
+
if (pluginInstance.prototype instanceof Plugin) {
|
|
232
|
+
pluginInstance = new pluginInstance();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
152
235
|
}
|
|
153
236
|
|
|
154
237
|
const name = pluginInstance.name;
|
|
@@ -187,7 +270,7 @@ class PluginManager {
|
|
|
187
270
|
this._log.info(`Loading plugin: ${name}`);
|
|
188
271
|
|
|
189
272
|
if (typeof pluginInstance.install === 'function') {
|
|
190
|
-
pluginInstance.install(this.framework);
|
|
273
|
+
await pluginInstance.install(this.framework);
|
|
191
274
|
}
|
|
192
275
|
|
|
193
276
|
entry.status = 'loaded';
|
|
@@ -246,14 +329,43 @@ class PluginManager {
|
|
|
246
329
|
this._log.info(`Reloading plugin: ${name}`);
|
|
247
330
|
const plugin = entry.instance;
|
|
248
331
|
|
|
332
|
+
// Refresh the plugin's prototype with the latest source code BEFORE invoking
|
|
333
|
+
// its reload hook. Without this, plugin.reload() still runs against the
|
|
334
|
+
// prototype cached at construction time (and Node.js' require cache means
|
|
335
|
+
// the same source file is returned on disk), so changes to plugin code
|
|
336
|
+
// (e.g., workflow/_loadWorkflows) won't take effect until the process
|
|
337
|
+
// restarts.
|
|
338
|
+
if (entry.sourcePath) {
|
|
339
|
+
try {
|
|
340
|
+
delete require.cache[require.resolve(entry.sourcePath)];
|
|
341
|
+
const mod = require(entry.sourcePath);
|
|
342
|
+
// The module may export a single class or an object map.
|
|
343
|
+
const PluginClass =
|
|
344
|
+
(mod && (mod.default || mod.WorkflowPlugin || mod)) ||
|
|
345
|
+
null;
|
|
346
|
+
if (
|
|
347
|
+
PluginClass &&
|
|
348
|
+
PluginClass.prototype &&
|
|
349
|
+
Object.getPrototypeOf(plugin) !== PluginClass.prototype
|
|
350
|
+
) {
|
|
351
|
+
Object.setPrototypeOf(plugin, PluginClass.prototype);
|
|
352
|
+
}
|
|
353
|
+
} catch (err) {
|
|
354
|
+
this._log.warn(
|
|
355
|
+
`Failed to refresh prototype for plugin '${name}' from ${entry.sourcePath}:`,
|
|
356
|
+
err.message
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
249
361
|
if (typeof plugin.reload === 'function') {
|
|
250
362
|
await plugin.reload(this.framework);
|
|
251
363
|
} else {
|
|
252
364
|
if (typeof plugin.uninstall === 'function') {
|
|
253
|
-
plugin.uninstall(this.framework);
|
|
365
|
+
await plugin.uninstall(this.framework);
|
|
254
366
|
}
|
|
255
367
|
if (typeof plugin.install === 'function') {
|
|
256
|
-
plugin.install(this.framework);
|
|
368
|
+
await plugin.install(this.framework);
|
|
257
369
|
}
|
|
258
370
|
if (typeof plugin.start === 'function') {
|
|
259
371
|
await plugin.start(this.framework);
|
|
@@ -342,10 +454,10 @@ class PluginManager {
|
|
|
342
454
|
} else {
|
|
343
455
|
// 没有 reload,走完整 uninstall + install + start
|
|
344
456
|
if (typeof entry.instance.uninstall === 'function') {
|
|
345
|
-
entry.instance.uninstall(this.framework);
|
|
457
|
+
await entry.instance.uninstall(this.framework);
|
|
346
458
|
}
|
|
347
459
|
if (typeof entry.instance.install === 'function') {
|
|
348
|
-
entry.instance.install(this.framework);
|
|
460
|
+
await entry.instance.install(this.framework);
|
|
349
461
|
}
|
|
350
462
|
if (typeof entry.instance.start === 'function') {
|
|
351
463
|
await entry.instance.start(this.framework);
|
|
@@ -405,6 +517,9 @@ class PluginManager {
|
|
|
405
517
|
}
|
|
406
518
|
}
|
|
407
519
|
|
|
520
|
+
// 清理该插件注册的提示词
|
|
521
|
+
entry.instance._cleanupPrompts?.();
|
|
522
|
+
|
|
408
523
|
this.framework?.emit('plugin:disabled', entry.instance);
|
|
409
524
|
this._saveState();
|
|
410
525
|
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
|
|
package/src/utils/sandbox.js
CHANGED
|
@@ -29,7 +29,7 @@ function createSandboxContext(customContext = {}, options = {}) {
|
|
|
29
29
|
Number: { isNaN: Number.isNaN, isFinite: Number.isFinite, parseInt: Number.parseInt, parseFloat: Number.parseFloat },
|
|
30
30
|
Boolean,
|
|
31
31
|
Object: { keys: Object.keys, values: Object.values, entries: Object.entries, assign: Object.assign, create: Object.create, freeze: Object.freeze },
|
|
32
|
-
Date
|
|
32
|
+
Date,
|
|
33
33
|
RegExp,
|
|
34
34
|
Error,
|
|
35
35
|
Promise,
|