foliko 2.0.11 → 2.0.13
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 +3 -1
- package/.editorconfig +56 -56
- package/.lintstagedrc +7 -7
- package/.prettierignore +29 -29
- package/.prettierrc +11 -11
- package/CLAUDE.md +1 -1
- package/Dockerfile +63 -63
- package/README.md +2 -2
- package/docs/extensions.md +3 -4
- package/docs/migration.md +3 -3
- package/docs/usage.md +3 -3
- package/install.ps1 +129 -129
- package/install.sh +121 -121
- package/package.json +1 -1
- package/plugins/core/scheduler/index.js +40 -9
- package/plugins/core/skill-manager/index.js +125 -151
- package/plugins/core/workflow/context.js +941 -941
- package/plugins/core/workflow/engine.js +66 -66
- package/plugins/core/workflow/js-runner.js +318 -318
- package/plugins/core/workflow/json-runner.js +323 -323
- package/plugins/core/workflow/stages/choice.js +74 -74
- package/plugins/core/workflow/stages/each.js +123 -123
- package/plugins/core/workflow/stages/parallel.js +69 -69
- package/plugins/executors/extension/prompt-builder.js +32 -5
- package/plugins/executors/shell/index.js +28 -0
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/skill-guide/SKILL.md +99 -60
- package/src/agent/chat.js +227 -54
- package/src/utils/message-validator.js +139 -16
- package/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.jsonl +0 -266
|
@@ -263,6 +263,59 @@ function buildSchemaFromType(type, desc, defaultValue) {
|
|
|
263
263
|
return schema;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
/**
|
|
267
|
+
* 从 options 构建 JSON Schema(供 AI SDK 直接使用,不走 Zod)
|
|
268
|
+
* 用于替换旧的 CLI 字符串模式,让 LLM 能用结构化参数调用
|
|
269
|
+
*
|
|
270
|
+
* type 可选值:'string' | 'number' | 'boolean' | 'array' | 'object'
|
|
271
|
+
* (对应 JS 原生类型 String, Boolean, Number, Array, Object)
|
|
272
|
+
*/
|
|
273
|
+
function buildInputSchemaFromOptions(options) {
|
|
274
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
275
|
+
return { type: 'object', properties: {}, required: [] };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const properties = {};
|
|
279
|
+
const required = [];
|
|
280
|
+
|
|
281
|
+
for (const opt of options) {
|
|
282
|
+
const name = extractOptionName(opt.flags);
|
|
283
|
+
if (!name) continue;
|
|
284
|
+
|
|
285
|
+
const hasValue = optionHasValue(opt.flags);
|
|
286
|
+
const isRequired = opt.required === true && hasValue;
|
|
287
|
+
const desc = opt.description || '';
|
|
288
|
+
const defaultVal = opt.defaultValue;
|
|
289
|
+
|
|
290
|
+
let jsonType = 'string';
|
|
291
|
+
if (opt.schema && typeof opt.schema.parse === 'function') {
|
|
292
|
+
// 从 Zod 实例猜测类型
|
|
293
|
+
const s = opt.schema;
|
|
294
|
+
if (s._def.typeName === 'ZodNumber' || s._def.typeName === 'ZodInt') jsonType = 'number';
|
|
295
|
+
else if (s._def.typeName === 'ZodBoolean') jsonType = 'boolean';
|
|
296
|
+
else if (s._def.typeName === 'ZodArray') jsonType = 'array';
|
|
297
|
+
else if (s._def.typeName === 'ZodObject' || s._def.typeName === 'ZodRecord') jsonType = 'object';
|
|
298
|
+
} else if (opt.type) {
|
|
299
|
+
const L = opt.type.toLowerCase();
|
|
300
|
+
if (L === 'number' || L === 'int' || L === 'integer') jsonType = 'number';
|
|
301
|
+
else if (L === 'boolean' || L === 'bool') jsonType = 'boolean';
|
|
302
|
+
else if (L === 'array') jsonType = 'array';
|
|
303
|
+
else if (L === 'object') jsonType = 'object';
|
|
304
|
+
} else {
|
|
305
|
+
jsonType = hasValue ? 'string' : 'boolean';
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const prop = { type: jsonType, description: desc };
|
|
309
|
+
if (defaultVal !== undefined && defaultVal !== null) {
|
|
310
|
+
prop.default = defaultVal;
|
|
311
|
+
}
|
|
312
|
+
properties[name] = prop;
|
|
313
|
+
if (isRequired) required.push(name);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { type: 'object', properties, required };
|
|
317
|
+
}
|
|
318
|
+
|
|
266
319
|
/**
|
|
267
320
|
* 生成等效的 JSON schema(用于 _rawSchema,让 ext_skill 渲染更清晰)
|
|
268
321
|
*/
|
|
@@ -329,6 +382,50 @@ function buildExampleFromOptions(options) {
|
|
|
329
382
|
return parts.join(' ');
|
|
330
383
|
}
|
|
331
384
|
|
|
385
|
+
/**
|
|
386
|
+
* 解析命令行参数字符串为结构化对象(供 messaging 插件 backward compatible 使用)
|
|
387
|
+
* 支持:--name value, -n value, --flag, -f
|
|
388
|
+
* @param {string} str - 参数字符串
|
|
389
|
+
* @param {Array} options - 选项定义数组
|
|
390
|
+
* @returns {Object} 解析后的参数对象
|
|
391
|
+
*/
|
|
392
|
+
function parseCommandArgs(str, options) {
|
|
393
|
+
if (typeof str !== 'string' || !str.trim()) return {};
|
|
394
|
+
const result = {};
|
|
395
|
+
const tokens = parseShellArgs(str);
|
|
396
|
+
let i = 0;
|
|
397
|
+
while (i < tokens.length) {
|
|
398
|
+
const token = tokens[i];
|
|
399
|
+
const optMatch = token.match(/^(-[a-zA-Z]|-{1,2}[a-zA-Z][a-zA-Z0-9-]*)$/);
|
|
400
|
+
if (optMatch) {
|
|
401
|
+
const optKey = optMatch[1];
|
|
402
|
+
const opt = options.find(o => {
|
|
403
|
+
const flags = o.flags || '';
|
|
404
|
+
return flags.includes(optKey) || flags.includes(optKey.replace(/^--?/, '--'));
|
|
405
|
+
});
|
|
406
|
+
if (opt) {
|
|
407
|
+
const name = extractOptionName(opt.flags);
|
|
408
|
+
const hasValue = optionHasValue(opt.flags);
|
|
409
|
+
if (hasValue && i + 1 < tokens.length) {
|
|
410
|
+
const next = tokens[i + 1];
|
|
411
|
+
if (!next.match(/^-/)) {
|
|
412
|
+
result[name] = next;
|
|
413
|
+
i += 2;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
} else if (!hasValue) {
|
|
417
|
+
result[name] = true;
|
|
418
|
+
i += 1;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
i += 1;
|
|
424
|
+
}
|
|
425
|
+
if (i < tokens.length) result._ = tokens.slice(i);
|
|
426
|
+
return result;
|
|
427
|
+
}
|
|
428
|
+
|
|
332
429
|
/**
|
|
333
430
|
* Skill 类
|
|
334
431
|
*/
|
|
@@ -356,6 +453,17 @@ class Skill {
|
|
|
356
453
|
return [];
|
|
357
454
|
}
|
|
358
455
|
|
|
456
|
+
/**
|
|
457
|
+
* 简单命令行字符串解析器(backward compatible,供 messaging 插件使用)
|
|
458
|
+
* 支持:--name value, -n value, --flag, -f, positional args
|
|
459
|
+
* @param {string} str - 参数字符串
|
|
460
|
+
* @param {Array} options - 选项定义数组
|
|
461
|
+
* @returns {Object} 解析后的参数对象
|
|
462
|
+
*/
|
|
463
|
+
_parseCommandString(str, options) {
|
|
464
|
+
return parseCommandArgs(str, options);
|
|
465
|
+
}
|
|
466
|
+
|
|
359
467
|
/**
|
|
360
468
|
* 注册 skill 的命令到 extension 系统
|
|
361
469
|
* 异步版本:用动态 import() 替代 require(),支持 ESM 包
|
|
@@ -415,87 +523,13 @@ class Skill {
|
|
|
415
523
|
// 命令名格式: {skill_name}:{cmd_name}
|
|
416
524
|
const fullName = `${skillName}:${cmd.name}`;
|
|
417
525
|
|
|
418
|
-
//
|
|
419
|
-
let argumentParser = cmd.argumentParser;
|
|
420
|
-
if (!argumentParser && Array.isArray(cmd.options) && cmd.options.length > 0) {
|
|
421
|
-
argumentParser = (rawArgs) => {
|
|
422
|
-
try {
|
|
423
|
-
const program = new Command();
|
|
424
|
-
program.exitOverride(); // 不自动退出
|
|
425
|
-
program.configureOutput({ writeErr: () => {} }); // 隐藏错误输出
|
|
426
|
-
for (const opt of cmd.options) {
|
|
427
|
-
program.option(opt.flags, opt.description, opt.defaultValue);
|
|
428
|
-
}
|
|
429
|
-
program.arguments('[args...]');
|
|
430
|
-
program.parse(['node', 'cmd', ...parseShellArgs(rawArgs)]);
|
|
431
|
-
return { ...program.opts(), args: program.args };
|
|
432
|
-
} catch (err) {
|
|
433
|
-
log.warn(`[SkillManager] Command parse error: ${err.message}`);
|
|
434
|
-
// Commander 解析失败时,用简单方式从 rawArgs 提取选项值
|
|
435
|
-
const result = {};
|
|
436
|
-
const tokens = parseShellArgs(rawArgs);
|
|
437
|
-
let i = 0;
|
|
438
|
-
while (i < tokens.length) {
|
|
439
|
-
const token = tokens[i];
|
|
440
|
-
// 匹配选项(-x 或 --xxx)
|
|
441
|
-
const optMatch = token.match(/^(-[a-zA-Z]|-{1,2}[a-zA-Z][a-zA-Z0-9-]*)$/);
|
|
442
|
-
if (optMatch) {
|
|
443
|
-
const optKey = optMatch[1];
|
|
444
|
-
// 找对应的 option
|
|
445
|
-
const opt = cmd.options.find(o => {
|
|
446
|
-
const flags = o.flags || '';
|
|
447
|
-
return flags.includes(optKey) || flags.includes(optKey.replace(/^--?/, '--'));
|
|
448
|
-
});
|
|
449
|
-
if (opt) {
|
|
450
|
-
const name = extractOptionName(opt.flags);
|
|
451
|
-
// 判断是否有值(下一个 token 不是选项)
|
|
452
|
-
const hasValue = /<[^>]+>/.test(opt.flags);
|
|
453
|
-
if (hasValue && i + 1 < tokens.length) {
|
|
454
|
-
const next = tokens[i + 1];
|
|
455
|
-
if (!next.match(/^-/)) {
|
|
456
|
-
result[name] = next;
|
|
457
|
-
i += 2;
|
|
458
|
-
continue;
|
|
459
|
-
} else {
|
|
460
|
-
// 下个 token 是另一个选项,但当前选项需要值,用默认值
|
|
461
|
-
result[name] = opt.defaultValue ?? undefined;
|
|
462
|
-
i += 1;
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
} else if (!hasValue) {
|
|
466
|
-
result[name] = true;
|
|
467
|
-
i += 1;
|
|
468
|
-
continue;
|
|
469
|
-
} else if (hasValue && i + 1 >= tokens.length) {
|
|
470
|
-
// 当前选项需要值但没有更多 token,用默认值
|
|
471
|
-
result[name] = opt.defaultValue ?? undefined;
|
|
472
|
-
i += 1;
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
i += 1;
|
|
478
|
-
}
|
|
479
|
-
// 剩余的 token 当 args
|
|
480
|
-
result.args = tokens.slice(i);
|
|
481
|
-
return result;
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// 直接使用 command 字符串的 schema,describe 说明命令行格式
|
|
487
|
-
const exampleArgs = cmd.options && cmd.options.length > 0
|
|
488
|
-
? buildExampleFromOptions(cmd.options)
|
|
489
|
-
: '';
|
|
526
|
+
// 新模式:结构化参数 schema,让 LLM 直接用 JSON 对象调用
|
|
490
527
|
const toolDef = {
|
|
491
528
|
name: cmd.name,
|
|
492
529
|
description: cmd.description || 'Skill command',
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}),
|
|
530
|
+
// 直接用 JSON Schema,不走 Zod(让 LLM 看到清晰的结构化参数)
|
|
531
|
+
inputSchema: buildInputSchemaFromOptions(cmd.options),
|
|
496
532
|
_options: cmd.options || null,
|
|
497
|
-
// 把原始 options 数组也保存下来,便于 ext_skill 显示
|
|
498
|
-
_rawSchema: buildRawJsonSchema(cmd.options),
|
|
499
533
|
execute: async (toolArgs, framework) => {
|
|
500
534
|
const ctx = {
|
|
501
535
|
sessionId: framework?._currentSessionId || 'unknown',
|
|
@@ -503,13 +537,13 @@ class Skill {
|
|
|
503
537
|
framework: framework,
|
|
504
538
|
cwd: framework?.getCwd?.() ?? process.cwd(),
|
|
505
539
|
};
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
let
|
|
509
|
-
if (typeof
|
|
510
|
-
|
|
540
|
+
// backward compatible:如果 args.command 是字符串(messaging 插件传来),
|
|
541
|
+
// 用 options 做简单解析;否则直接传递结构化参数(AI SDK / ext_call 传来)
|
|
542
|
+
let args = toolArgs || {};
|
|
543
|
+
if (typeof args.command === 'string' && Array.isArray(cmd.options) && cmd.options.length > 0) {
|
|
544
|
+
args = parseCommandArgs(args.command, cmd.options);
|
|
511
545
|
}
|
|
512
|
-
return await handler(
|
|
546
|
+
return await handler(args, ctx);
|
|
513
547
|
},
|
|
514
548
|
};
|
|
515
549
|
|
|
@@ -1283,72 +1317,12 @@ class SkillManagerPlugin extends Plugin {
|
|
|
1283
1317
|
|
|
1284
1318
|
const fullName = `${name}:${cmd.name}`;
|
|
1285
1319
|
|
|
1286
|
-
//
|
|
1287
|
-
let argumentParser = cmd.argumentParser;
|
|
1288
|
-
if (!argumentParser && Array.isArray(cmd.options) && cmd.options.length > 0) {
|
|
1289
|
-
argumentParser = (rawArgs) => {
|
|
1290
|
-
try {
|
|
1291
|
-
const program = new Command();
|
|
1292
|
-
program.exitOverride();
|
|
1293
|
-
program.configureOutput({ writeErr: () => {} });
|
|
1294
|
-
for (const opt of cmd.options) {
|
|
1295
|
-
program.option(opt.flags, opt.description, opt.defaultValue);
|
|
1296
|
-
}
|
|
1297
|
-
program.arguments('[args...]');
|
|
1298
|
-
program.parse(['node', 'cmd', ...parseShellArgs(rawArgs)]);
|
|
1299
|
-
return { ...program.opts(), args: program.args };
|
|
1300
|
-
} catch (err) {
|
|
1301
|
-
log.warn(`[SkillManager] Command parse error: ${err.message}`);
|
|
1302
|
-
// Commander 解析失败时,用简单方式从 rawArgs 提取选项值
|
|
1303
|
-
const result = {};
|
|
1304
|
-
const tokens = parseShellArgs(rawArgs);
|
|
1305
|
-
let i = 0;
|
|
1306
|
-
while (i < tokens.length) {
|
|
1307
|
-
const token = tokens[i];
|
|
1308
|
-
const optMatch = token.match(/^(-[a-zA-Z]|-{1,2}[a-zA-Z][a-zA-Z0-9-]*)$/);
|
|
1309
|
-
if (optMatch) {
|
|
1310
|
-
const optKey = optMatch[1];
|
|
1311
|
-
const opt = cmd.options.find(o => {
|
|
1312
|
-
const flags = o.flags || '';
|
|
1313
|
-
return flags.includes(optKey) || flags.includes(optKey.replace(/^--?/, '--'));
|
|
1314
|
-
});
|
|
1315
|
-
if (opt) {
|
|
1316
|
-
const name = extractOptionName(opt.flags);
|
|
1317
|
-
const hasValue = /<[^>]+>/.test(opt.flags);
|
|
1318
|
-
if (hasValue && i + 1 < tokens.length) {
|
|
1319
|
-
const next = tokens[i + 1];
|
|
1320
|
-
if (!next.match(/^-/)) {
|
|
1321
|
-
result[name] = next;
|
|
1322
|
-
i += 2;
|
|
1323
|
-
continue;
|
|
1324
|
-
}
|
|
1325
|
-
} else if (!hasValue) {
|
|
1326
|
-
result[name] = true;
|
|
1327
|
-
i += 1;
|
|
1328
|
-
continue;
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
i += 1;
|
|
1333
|
-
}
|
|
1334
|
-
result.args = tokens.slice(i);
|
|
1335
|
-
return result;
|
|
1336
|
-
}
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
// 转为 Zod schema(统一风格)
|
|
1341
|
-
const exampleArgs2 = cmd.options && cmd.options.length > 0
|
|
1342
|
-
? buildExampleFromOptions(cmd.options)
|
|
1343
|
-
: '';
|
|
1320
|
+
// 新模式:结构化参数 schema
|
|
1344
1321
|
const toolDef = {
|
|
1345
|
-
name: cmd.name,
|
|
1322
|
+
name: cmd.name,
|
|
1346
1323
|
description: cmd.description || 'Skill command',
|
|
1347
|
-
inputSchema:
|
|
1348
|
-
command: z.string().optional().describe(exampleArgs2 || '命令行字符串'),
|
|
1349
|
-
}),
|
|
1324
|
+
inputSchema: buildInputSchemaFromOptions(cmd.options),
|
|
1350
1325
|
_options: cmd.options || null,
|
|
1351
|
-
_rawSchema: buildRawJsonSchema(cmd.options),
|
|
1352
1326
|
execute: async (toolArgs, framework) => {
|
|
1353
1327
|
const ctx = {
|
|
1354
1328
|
sessionId: framework?._currentSessionId || 'unknown',
|
|
@@ -1356,12 +1330,12 @@ class SkillManagerPlugin extends Plugin {
|
|
|
1356
1330
|
framework: framework,
|
|
1357
1331
|
cwd: framework?.getCwd?.() ?? process.cwd(),
|
|
1358
1332
|
};
|
|
1359
|
-
|
|
1360
|
-
let
|
|
1361
|
-
if (typeof
|
|
1362
|
-
|
|
1333
|
+
// backward compatible:如果 args.command 是字符串,解析为结构化对象
|
|
1334
|
+
let args = toolArgs || {};
|
|
1335
|
+
if (typeof args.command === 'string' && Array.isArray(cmd.options) && cmd.options.length > 0) {
|
|
1336
|
+
args = parseCommandArgs(args.command, cmd.options);
|
|
1363
1337
|
}
|
|
1364
|
-
return await handler(
|
|
1338
|
+
return await handler(args, ctx);
|
|
1365
1339
|
},
|
|
1366
1340
|
};
|
|
1367
1341
|
|