skill-auto-loader-hook-paperfly777 0.1.8 → 0.1.10
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/index.ts +106 -38
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -65,6 +65,13 @@ function safeString(value: unknown): string {
|
|
|
65
65
|
return typeof value === "string" ? value : "";
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function shortText(text: string, max = 120): string {
|
|
69
|
+
if (!text) {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
// 工具函数:把 "~/" 展开为用户 home 目录绝对路径
|
|
69
76
|
function expandHome(inputPath: string): string {
|
|
70
77
|
if (!inputPath) {
|
|
@@ -374,49 +381,71 @@ async function callDecisionModel(
|
|
|
374
381
|
|
|
375
382
|
let text = "";
|
|
376
383
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
384
|
+
try {
|
|
385
|
+
if (modelCfg.apiType === "openai-completions") {
|
|
386
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
387
|
+
method: "POST",
|
|
388
|
+
headers,
|
|
389
|
+
body: JSON.stringify({
|
|
390
|
+
model: modelCfg.modelId,
|
|
391
|
+
temperature: 0,
|
|
392
|
+
max_tokens: 300,
|
|
393
|
+
response_format: { type: "json_object" },
|
|
394
|
+
messages: [
|
|
395
|
+
{ role: "system", content: system },
|
|
396
|
+
{ role: "user", content: user },
|
|
397
|
+
],
|
|
398
|
+
}),
|
|
399
|
+
});
|
|
400
|
+
const data = (await response.json()) as any;
|
|
401
|
+
text = safeString(data?.choices?.[0]?.message?.content);
|
|
402
|
+
} else if (modelCfg.apiType === "openai-responses") {
|
|
403
|
+
const response = await fetch(`${baseUrl}/responses`, {
|
|
404
|
+
method: "POST",
|
|
405
|
+
headers,
|
|
406
|
+
body: JSON.stringify({
|
|
407
|
+
model: modelCfg.modelId,
|
|
408
|
+
temperature: 0,
|
|
409
|
+
max_output_tokens: 300,
|
|
410
|
+
input: [
|
|
411
|
+
{ role: "system", content: [{ type: "input_text", text: system }] },
|
|
412
|
+
{ role: "user", content: [{ type: "input_text", text: user }] },
|
|
413
|
+
],
|
|
414
|
+
}),
|
|
415
|
+
});
|
|
416
|
+
const data = (await response.json()) as any;
|
|
417
|
+
text =
|
|
418
|
+
safeString(data?.output_text) ||
|
|
419
|
+
safeString(data?.output?.[0]?.content?.[0]?.text) ||
|
|
420
|
+
safeString(data?.choices?.[0]?.message?.content);
|
|
421
|
+
} else {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
414
425
|
return null;
|
|
415
426
|
}
|
|
416
427
|
|
|
417
428
|
return parseDecisionJson(text, candidateSkills);
|
|
418
429
|
}
|
|
419
430
|
|
|
431
|
+
function fallbackDecisionFromRules(candidateSkills: SkillMeta[], matchedRules: RoutingRule[]): ModelDecision | null {
|
|
432
|
+
const preferred = new Set<string>();
|
|
433
|
+
for (const rule of matchedRules) {
|
|
434
|
+
(rule.preferSkills || []).forEach((name) => preferred.add(name));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const selected = candidateSkills.filter((skill) => preferred.has(skill.name)).slice(0, 3);
|
|
438
|
+
if (selected.length === 0) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
use_skill: true,
|
|
444
|
+
selected_skills: selected.map((skill) => skill.name),
|
|
445
|
+
reason: "模型路由未返回有效结果,已回退到规则 preferSkills。",
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
420
449
|
// 解析 SKILL.md 头部 frontmatter(name/description)
|
|
421
450
|
function parseYamlLikeFrontmatter(raw: string): { name: string; description: string } {
|
|
422
451
|
const result = { name: "", description: "" };
|
|
@@ -661,19 +690,25 @@ export default definePluginEntry({
|
|
|
661
690
|
// 步骤 1:读取插件配置与独立路由配置
|
|
662
691
|
const cfg = normalizePluginConfig(api);
|
|
663
692
|
if (!cfg.enabled) {
|
|
693
|
+
api.logger?.debug?.("skill-auto-loader-hook: plugin disabled, skip");
|
|
664
694
|
return undefined;
|
|
665
695
|
}
|
|
666
696
|
|
|
667
697
|
const routerCfg = readRouterConfig(cfg.routerConfigPath!, __dirname);
|
|
668
698
|
if (routerCfg.enabled === false) {
|
|
699
|
+
api.logger?.debug?.("skill-auto-loader-hook: router config disabled, skip");
|
|
669
700
|
return undefined;
|
|
670
701
|
}
|
|
671
702
|
|
|
672
703
|
// 步骤 2:提取本轮用户输入
|
|
673
704
|
const userText = extractLatestUserText(event);
|
|
674
705
|
if (!userText) {
|
|
706
|
+
api.logger?.debug?.("skill-auto-loader-hook: no user text extracted, skip");
|
|
675
707
|
return undefined;
|
|
676
708
|
}
|
|
709
|
+
if (routerCfg.logRouting) {
|
|
710
|
+
api.logger?.info?.(`skill-auto-loader-hook: extracted userText="${shortText(userText)}"`);
|
|
711
|
+
}
|
|
677
712
|
|
|
678
713
|
// 步骤 3:扫描并收集可用 skill
|
|
679
714
|
const scanDirs =
|
|
@@ -684,19 +719,52 @@ export default definePluginEntry({
|
|
|
684
719
|
const allSkills = scanInstalledSkills(scanDirs);
|
|
685
720
|
const matchedRules = (routerCfg.rules || []).filter((rule) => rule.enabled !== false && matchesRule(userText, rule));
|
|
686
721
|
const defaultBehavior = routerCfg.defaultBehavior || "no-op";
|
|
722
|
+
if (routerCfg.logRouting) {
|
|
723
|
+
const ruleIds = matchedRules.map((rule) => rule.id).join(",") || "none";
|
|
724
|
+
api.logger?.info?.(`skill-auto-loader-hook: matchedRules=${ruleIds} defaultBehavior=${defaultBehavior}`);
|
|
725
|
+
}
|
|
687
726
|
// 步骤 4:默认 no-op 时,未命中规则就不注入(避免每轮追加)
|
|
688
727
|
if (matchedRules.length === 0 && defaultBehavior === "no-op") {
|
|
728
|
+
api.logger?.debug?.("skill-auto-loader-hook: no rules matched and defaultBehavior=no-op, skip");
|
|
689
729
|
return undefined;
|
|
690
730
|
}
|
|
691
731
|
// 步骤 5:先根据规则收缩候选 skill 范围
|
|
692
732
|
const candidateSkills = applyRuleFiltering(allSkills, routerCfg, matchedRules);
|
|
733
|
+
if (routerCfg.logRouting) {
|
|
734
|
+
api.logger?.info?.(
|
|
735
|
+
`skill-auto-loader-hook: candidateSkills=${candidateSkills.map((skill) => skill.name).join(",") || "none"}`,
|
|
736
|
+
);
|
|
737
|
+
}
|
|
693
738
|
if (candidateSkills.length === 0) {
|
|
739
|
+
api.logger?.debug?.("skill-auto-loader-hook: no candidate skills after filtering, skip");
|
|
694
740
|
return undefined;
|
|
695
741
|
}
|
|
696
742
|
|
|
697
743
|
// 步骤 6:主动调用一次默认大模型,让模型在候选 skill 中做最终路由判断
|
|
698
|
-
|
|
744
|
+
if (routerCfg.logRouting) {
|
|
745
|
+
api.logger?.info?.("skill-auto-loader-hook: calling decision model");
|
|
746
|
+
}
|
|
747
|
+
let decision = await callDecisionModel(cfg.openclawConfigPath!, userText, candidateSkills, matchedRules, routerCfg);
|
|
748
|
+
if (routerCfg.logRouting) {
|
|
749
|
+
api.logger?.info?.(
|
|
750
|
+
`skill-auto-loader-hook: decisionModelResult=${decision ? JSON.stringify(decision) : "null"}`,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
if ((!decision || !decision.use_skill || decision.selected_skills.length === 0) && matchedRules.length > 0) {
|
|
754
|
+
if (routerCfg.logRouting) {
|
|
755
|
+
api.logger?.info?.("skill-auto-loader-hook: model decision empty, fallback to preferSkills");
|
|
756
|
+
}
|
|
757
|
+
decision = fallbackDecisionFromRules(candidateSkills, matchedRules);
|
|
758
|
+
if (routerCfg.logRouting) {
|
|
759
|
+
api.logger?.info?.(
|
|
760
|
+
`skill-auto-loader-hook: fallbackDecision=${decision ? JSON.stringify(decision) : "null"}`,
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
699
764
|
if (!decision || !decision.use_skill || decision.selected_skills.length === 0) {
|
|
765
|
+
if (routerCfg.logRouting) {
|
|
766
|
+
api.logger?.info?.("skill-auto-loader-hook: no valid decision returned by model and no fallback applied");
|
|
767
|
+
}
|
|
700
768
|
return undefined;
|
|
701
769
|
}
|
|
702
770
|
|
package/package.json
CHANGED