@memtensor/memos-local-openclaw-plugin 0.1.4 → 0.1.5
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 +196 -84
- package/dist/ingest/dedup.d.ts +8 -0
- package/dist/ingest/dedup.d.ts.map +1 -1
- package/dist/ingest/dedup.js +21 -0
- package/dist/ingest/dedup.js.map +1 -1
- package/dist/ingest/providers/anthropic.d.ts +14 -0
- package/dist/ingest/providers/anthropic.d.ts.map +1 -1
- package/dist/ingest/providers/anthropic.js +104 -0
- package/dist/ingest/providers/anthropic.js.map +1 -1
- package/dist/ingest/providers/bedrock.d.ts +14 -0
- package/dist/ingest/providers/bedrock.d.ts.map +1 -1
- package/dist/ingest/providers/bedrock.js +100 -0
- package/dist/ingest/providers/bedrock.js.map +1 -1
- package/dist/ingest/providers/gemini.d.ts +14 -0
- package/dist/ingest/providers/gemini.d.ts.map +1 -1
- package/dist/ingest/providers/gemini.js +96 -0
- package/dist/ingest/providers/gemini.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +22 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +68 -0
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +22 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +143 -0
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.d.ts +2 -0
- package/dist/ingest/task-processor.d.ts.map +1 -1
- package/dist/ingest/task-processor.js +15 -0
- package/dist/ingest/task-processor.js.map +1 -1
- package/dist/ingest/worker.d.ts +2 -0
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +115 -12
- package/dist/ingest/worker.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +1 -0
- package/dist/recall/engine.js.map +1 -1
- package/dist/skill/bundled-memory-guide.d.ts +6 -0
- package/dist/skill/bundled-memory-guide.d.ts.map +1 -0
- package/dist/skill/bundled-memory-guide.js +95 -0
- package/dist/skill/bundled-memory-guide.js.map +1 -0
- package/dist/skill/evaluator.d.ts +31 -0
- package/dist/skill/evaluator.d.ts.map +1 -0
- package/dist/skill/evaluator.js +194 -0
- package/dist/skill/evaluator.js.map +1 -0
- package/dist/skill/evolver.d.ts +22 -0
- package/dist/skill/evolver.d.ts.map +1 -0
- package/dist/skill/evolver.js +193 -0
- package/dist/skill/evolver.js.map +1 -0
- package/dist/skill/generator.d.ts +25 -0
- package/dist/skill/generator.d.ts.map +1 -0
- package/dist/skill/generator.js +477 -0
- package/dist/skill/generator.js.map +1 -0
- package/dist/skill/installer.d.ts +16 -0
- package/dist/skill/installer.d.ts.map +1 -0
- package/dist/skill/installer.js +89 -0
- package/dist/skill/installer.js.map +1 -0
- package/dist/skill/upgrader.d.ts +19 -0
- package/dist/skill/upgrader.d.ts.map +1 -0
- package/dist/skill/upgrader.js +263 -0
- package/dist/skill/upgrader.js.map +1 -0
- package/dist/skill/validator.d.ts +29 -0
- package/dist/skill/validator.d.ts.map +1 -0
- package/dist/skill/validator.js +227 -0
- package/dist/skill/validator.js.map +1 -0
- package/dist/storage/sqlite.d.ts +75 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +417 -6
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +1549 -113
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +13 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +289 -4
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +489 -181
- package/package.json +1 -1
- package/skill/memos-memory-guide/SKILL.md +86 -0
- package/src/ingest/dedup.ts +29 -0
- package/src/ingest/providers/anthropic.ts +130 -0
- package/src/ingest/providers/bedrock.ts +126 -0
- package/src/ingest/providers/gemini.ts +124 -0
- package/src/ingest/providers/index.ts +86 -4
- package/src/ingest/providers/openai.ts +174 -0
- package/src/ingest/task-processor.ts +16 -0
- package/src/ingest/worker.ts +126 -21
- package/src/recall/engine.ts +1 -0
- package/src/skill/bundled-memory-guide.ts +91 -0
- package/src/skill/evaluator.ts +220 -0
- package/src/skill/evolver.ts +169 -0
- package/src/skill/generator.ts +506 -0
- package/src/skill/installer.ts +59 -0
- package/src/skill/upgrader.ts +257 -0
- package/src/skill/validator.ts +227 -0
- package/src/storage/sqlite.ts +508 -6
- package/src/types.ts +77 -0
- package/src/viewer/html.ts +1549 -113
- package/src/viewer/server.ts +285 -4
- package/skill/SKILL.md +0 -59
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { Chunk, Task, Skill, PluginContext, SummarizerConfig } from "../types";
|
|
2
|
+
import { DEFAULTS } from "../types";
|
|
3
|
+
|
|
4
|
+
export interface CreateEvalResult {
|
|
5
|
+
shouldGenerate: boolean;
|
|
6
|
+
reason: string;
|
|
7
|
+
suggestedName: string;
|
|
8
|
+
suggestedTags: string[];
|
|
9
|
+
confidence: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UpgradeEvalResult {
|
|
13
|
+
shouldUpgrade: boolean;
|
|
14
|
+
upgradeType: "refine" | "extend" | "fix";
|
|
15
|
+
dimensions: string[];
|
|
16
|
+
reason: string;
|
|
17
|
+
mergeStrategy: string;
|
|
18
|
+
confidence: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CREATE_EVAL_PROMPT = `You are an experience evaluation expert. Based on the completed task record below, decide whether this task contains reusable experience worth distilling into a "skill".
|
|
22
|
+
|
|
23
|
+
A skill is a reusable guide that helps an AI agent handle similar tasks better in the future.
|
|
24
|
+
|
|
25
|
+
Worth distilling (any ONE qualifies):
|
|
26
|
+
- Contains concrete steps, commands, code, or configuration
|
|
27
|
+
- Solves a recurring problem with a specific approach/workflow
|
|
28
|
+
- Went through trial-and-error (wrong approach then corrected)
|
|
29
|
+
- Involves non-obvious usage of specific tools, APIs, or frameworks
|
|
30
|
+
- Contains debugging/troubleshooting with diagnostic reasoning
|
|
31
|
+
- Demonstrates a multi-step workflow using external tools (browser, search, file system, etc.)
|
|
32
|
+
- Reveals user preferences or style requirements that should be remembered
|
|
33
|
+
- Shows how to combine multiple tools/services to accomplish a goal
|
|
34
|
+
- Contains a process that required specific parameter tuning or configuration
|
|
35
|
+
|
|
36
|
+
NOT worth distilling:
|
|
37
|
+
- Pure factual Q&A with no process ("what is TCP", "what's the capital of France")
|
|
38
|
+
- Single-turn simple answers with no workflow
|
|
39
|
+
- Conversation too fragmented or incoherent to extract a clear process
|
|
40
|
+
|
|
41
|
+
Task title: {TITLE}
|
|
42
|
+
Task summary:
|
|
43
|
+
{SUMMARY}
|
|
44
|
+
|
|
45
|
+
Reply in JSON only, no extra text:
|
|
46
|
+
{
|
|
47
|
+
"shouldGenerate": boolean,
|
|
48
|
+
"reason": "brief explanation",
|
|
49
|
+
"suggestedName": "kebab-case-name",
|
|
50
|
+
"suggestedTags": ["tag1", "tag2"],
|
|
51
|
+
"confidence": 0.0-1.0
|
|
52
|
+
}`;
|
|
53
|
+
|
|
54
|
+
const UPGRADE_EVAL_PROMPT = `You are a skill upgrade evaluation expert.
|
|
55
|
+
|
|
56
|
+
Existing skill (v{VERSION}):
|
|
57
|
+
Name: {SKILL_NAME}
|
|
58
|
+
Content:
|
|
59
|
+
{SKILL_CONTENT}
|
|
60
|
+
|
|
61
|
+
Newly completed task:
|
|
62
|
+
Title: {TITLE}
|
|
63
|
+
Summary:
|
|
64
|
+
{SUMMARY}
|
|
65
|
+
|
|
66
|
+
Does the new task bring substantive improvements to the existing skill?
|
|
67
|
+
|
|
68
|
+
Worth upgrading (any one qualifies):
|
|
69
|
+
1. Faster — shorter path discovered
|
|
70
|
+
2. More elegant — cleaner, follows best practices better
|
|
71
|
+
3. More convenient — fewer dependencies or complexity
|
|
72
|
+
4. Fewer tokens — less exploration/trial-and-error needed
|
|
73
|
+
5. More accurate — corrects wrong parameters/steps in old skill
|
|
74
|
+
6. More robust — adds edge cases, error handling
|
|
75
|
+
7. New scenario — covers a variant the old skill didn't
|
|
76
|
+
8. Fixes outdated info — old skill has stale information
|
|
77
|
+
|
|
78
|
+
NOT worth upgrading:
|
|
79
|
+
- New task is identical to existing skill
|
|
80
|
+
- New task's approach is worse than existing skill
|
|
81
|
+
- Differences are trivial
|
|
82
|
+
|
|
83
|
+
Reply in JSON only, no extra text:
|
|
84
|
+
{
|
|
85
|
+
"shouldUpgrade": boolean,
|
|
86
|
+
"upgradeType": "refine" | "extend" | "fix",
|
|
87
|
+
"dimensions": ["faster", "more_elegant", "more_convenient", "fewer_tokens", "more_accurate", "more_robust", "new_scenario", "fix_outdated"],
|
|
88
|
+
"reason": "what new value the task brings",
|
|
89
|
+
"mergeStrategy": "which specific parts need updating",
|
|
90
|
+
"confidence": 0.0-1.0
|
|
91
|
+
}`;
|
|
92
|
+
|
|
93
|
+
export class SkillEvaluator {
|
|
94
|
+
constructor(private ctx: PluginContext) {}
|
|
95
|
+
|
|
96
|
+
passesRuleFilter(chunks: Chunk[], task: Task): { pass: boolean; skipReason: string } {
|
|
97
|
+
const minChunks = this.ctx.config.skillEvolution?.minChunksForEval ?? DEFAULTS.skillMinChunksForEval;
|
|
98
|
+
if (chunks.length < minChunks) {
|
|
99
|
+
return { pass: false, skipReason: `chunks不足 (${chunks.length} < ${minChunks})` };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (task.status === "skipped") {
|
|
103
|
+
return { pass: false, skipReason: "task状态为skipped" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (task.summary.length < 100) {
|
|
107
|
+
return { pass: false, skipReason: `summary过短 (${task.summary.length} < 100)` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const userChunks = chunks.filter(c => c.role === "user");
|
|
111
|
+
if (userChunks.length === 0) {
|
|
112
|
+
return { pass: false, skipReason: "无用户消息" };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const assistantChunks = chunks.filter(c => c.role === "assistant");
|
|
116
|
+
if (assistantChunks.length === 0) {
|
|
117
|
+
return { pass: false, skipReason: "无助手回复" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { pass: true, skipReason: "" };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async evaluateCreate(task: Task): Promise<CreateEvalResult> {
|
|
124
|
+
const cfg = this.getProviderConfig();
|
|
125
|
+
if (!cfg) {
|
|
126
|
+
return { shouldGenerate: false, reason: "no LLM configured", suggestedName: "", suggestedTags: [], confidence: 0 };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const prompt = CREATE_EVAL_PROMPT
|
|
130
|
+
.replace("{TITLE}", task.title)
|
|
131
|
+
.replace("{SUMMARY}", task.summary.slice(0, 3000));
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const raw = await this.callLLM(cfg, prompt);
|
|
135
|
+
return this.parseJSON<CreateEvalResult>(raw, {
|
|
136
|
+
shouldGenerate: false, reason: "parse failed", suggestedName: "", suggestedTags: [], confidence: 0,
|
|
137
|
+
});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
this.ctx.log.warn(`SkillEvaluator.evaluateCreate failed: ${err}`);
|
|
140
|
+
return { shouldGenerate: false, reason: `error: ${err}`, suggestedName: "", suggestedTags: [], confidence: 0 };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async evaluateUpgrade(task: Task, skill: Skill, skillContent: string): Promise<UpgradeEvalResult> {
|
|
145
|
+
const cfg = this.getProviderConfig();
|
|
146
|
+
if (!cfg) {
|
|
147
|
+
return { shouldUpgrade: false, upgradeType: "refine", dimensions: [], reason: "no LLM configured", mergeStrategy: "", confidence: 0 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const prompt = UPGRADE_EVAL_PROMPT
|
|
151
|
+
.replace("{VERSION}", String(skill.version))
|
|
152
|
+
.replace("{SKILL_NAME}", skill.name)
|
|
153
|
+
.replace("{SKILL_CONTENT}", skillContent.slice(0, 4000))
|
|
154
|
+
.replace("{TITLE}", task.title)
|
|
155
|
+
.replace("{SUMMARY}", task.summary.slice(0, 3000));
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const raw = await this.callLLM(cfg, prompt);
|
|
159
|
+
return this.parseJSON<UpgradeEvalResult>(raw, {
|
|
160
|
+
shouldUpgrade: false, upgradeType: "refine", dimensions: [], reason: "parse failed", mergeStrategy: "", confidence: 0,
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
this.ctx.log.warn(`SkillEvaluator.evaluateUpgrade failed: ${err}`);
|
|
164
|
+
return { shouldUpgrade: false, upgradeType: "refine", dimensions: [], reason: `error: ${err}`, mergeStrategy: "", confidence: 0 };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private getProviderConfig(): SummarizerConfig | undefined {
|
|
169
|
+
return this.ctx.config.skillEvolution?.summarizer ?? this.ctx.config.summarizer;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async callLLM(cfg: SummarizerConfig, userContent: string): Promise<string> {
|
|
173
|
+
const endpoint = this.normalizeEndpoint(cfg.endpoint ?? "https://api.openai.com/v1/chat/completions");
|
|
174
|
+
const model = cfg.model ?? "gpt-4o-mini";
|
|
175
|
+
const headers: Record<string, string> = {
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
178
|
+
...cfg.headers,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const resp = await fetch(endpoint, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
headers,
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
model,
|
|
186
|
+
temperature: cfg.temperature ?? 0.1,
|
|
187
|
+
max_tokens: 1024,
|
|
188
|
+
messages: [
|
|
189
|
+
{ role: "user", content: userContent },
|
|
190
|
+
],
|
|
191
|
+
}),
|
|
192
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 30_000),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!resp.ok) {
|
|
196
|
+
const body = await resp.text();
|
|
197
|
+
throw new Error(`LLM call failed (${resp.status}): ${body}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const json = (await resp.json()) as { choices: Array<{ message: { content: string } }> };
|
|
201
|
+
return json.choices[0]?.message?.content?.trim() ?? "";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private parseJSON<T>(raw: string, fallback: T): T {
|
|
205
|
+
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
206
|
+
if (!jsonMatch) return fallback;
|
|
207
|
+
try {
|
|
208
|
+
return JSON.parse(jsonMatch[0]) as T;
|
|
209
|
+
} catch {
|
|
210
|
+
return fallback;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private normalizeEndpoint(url: string): string {
|
|
215
|
+
const stripped = url.replace(/\/+$/, "");
|
|
216
|
+
if (stripped.endsWith("/chat/completions")) return stripped;
|
|
217
|
+
if (stripped.endsWith("/completions")) return stripped;
|
|
218
|
+
return `${stripped}/chat/completions`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import type { SqliteStore } from "../storage/sqlite";
|
|
4
|
+
import type { RecallEngine } from "../recall/engine";
|
|
5
|
+
import type { Task, Skill, Chunk, PluginContext } from "../types";
|
|
6
|
+
import { DEFAULTS } from "../types";
|
|
7
|
+
import { SkillEvaluator } from "./evaluator";
|
|
8
|
+
import { SkillGenerator } from "./generator";
|
|
9
|
+
import { SkillUpgrader } from "./upgrader";
|
|
10
|
+
import { SkillInstaller } from "./installer";
|
|
11
|
+
|
|
12
|
+
export class SkillEvolver {
|
|
13
|
+
private evaluator: SkillEvaluator;
|
|
14
|
+
private generator: SkillGenerator;
|
|
15
|
+
private upgrader: SkillUpgrader;
|
|
16
|
+
private installer: SkillInstaller;
|
|
17
|
+
private processing = false;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private store: SqliteStore,
|
|
21
|
+
private engine: RecallEngine,
|
|
22
|
+
private ctx: PluginContext,
|
|
23
|
+
) {
|
|
24
|
+
this.evaluator = new SkillEvaluator(ctx);
|
|
25
|
+
this.generator = new SkillGenerator(store, engine, ctx);
|
|
26
|
+
this.upgrader = new SkillUpgrader(store, ctx);
|
|
27
|
+
this.installer = new SkillInstaller(store, ctx);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async onTaskCompleted(task: Task): Promise<void> {
|
|
31
|
+
const enabled = this.ctx.config.skillEvolution?.enabled ?? DEFAULTS.skillEvolutionEnabled;
|
|
32
|
+
const autoEval = this.ctx.config.skillEvolution?.autoEvaluate ?? DEFAULTS.skillAutoEvaluate;
|
|
33
|
+
if (!enabled || !autoEval) return;
|
|
34
|
+
|
|
35
|
+
if (this.processing) {
|
|
36
|
+
this.ctx.log.debug("SkillEvolver: already processing, skipping");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.processing = true;
|
|
40
|
+
try {
|
|
41
|
+
await this.process(task);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
this.ctx.log.error(`SkillEvolver error: ${err}`);
|
|
44
|
+
} finally {
|
|
45
|
+
this.processing = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async process(task: Task): Promise<void> {
|
|
50
|
+
const chunks = this.store.getChunksByTask(task.id);
|
|
51
|
+
|
|
52
|
+
const { pass, skipReason } = this.evaluator.passesRuleFilter(chunks, task);
|
|
53
|
+
if (!pass) {
|
|
54
|
+
this.ctx.log.debug(`SkillEvolver: task ${task.id} skipped by rule filter: ${skipReason} (chunks=${chunks.length})`);
|
|
55
|
+
this.store.setTaskSkillMeta(task.id, { skillStatus: "skipped", skillReason: skipReason });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const relatedSkill = await this.findRelatedSkill(task);
|
|
60
|
+
|
|
61
|
+
if (relatedSkill) {
|
|
62
|
+
await this.handleExistingSkill(task, chunks, relatedSkill);
|
|
63
|
+
} else {
|
|
64
|
+
await this.handleNewSkill(task, chunks);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async findRelatedSkill(task: Task): Promise<Skill | null> {
|
|
69
|
+
try {
|
|
70
|
+
const result = await this.engine.search({
|
|
71
|
+
query: task.summary.slice(0, 500),
|
|
72
|
+
maxResults: 10,
|
|
73
|
+
minScore: 0.5,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
for (const hit of result.hits) {
|
|
77
|
+
if (hit.skillId) {
|
|
78
|
+
const skill = this.store.getSkill(hit.skillId);
|
|
79
|
+
if (skill && (skill.status === "active" || skill.status === "draft")) {
|
|
80
|
+
this.ctx.log.debug(`SkillEvolver: found related skill "${skill.name}" via memory search`);
|
|
81
|
+
return skill;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
this.ctx.log.warn(`SkillEvolver: memory search for related skill failed: ${err}`);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async handleExistingSkill(task: Task, chunks: Chunk[], skill: Skill): Promise<void> {
|
|
92
|
+
const skillContent = this.readSkillContent(skill);
|
|
93
|
+
if (!skillContent) {
|
|
94
|
+
this.ctx.log.warn(`SkillEvolver: cannot read skill "${skill.name}" content, treating as new`);
|
|
95
|
+
await this.handleNewSkill(task, chunks);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const minConfidence = this.ctx.config.skillEvolution?.minConfidence ?? DEFAULTS.skillMinConfidence;
|
|
100
|
+
const evalResult = await this.evaluator.evaluateUpgrade(task, skill, skillContent);
|
|
101
|
+
|
|
102
|
+
if (evalResult.shouldUpgrade && evalResult.confidence >= minConfidence) {
|
|
103
|
+
this.ctx.log.info(`SkillEvolver: upgrading skill "${skill.name}" — ${evalResult.reason}`);
|
|
104
|
+
const { upgraded } = await this.upgrader.upgrade(task, skill, evalResult);
|
|
105
|
+
|
|
106
|
+
this.markChunksWithSkill(chunks, skill.id);
|
|
107
|
+
|
|
108
|
+
if (upgraded) {
|
|
109
|
+
this.store.linkTaskSkill(task.id, skill.id, "evolved_from", skill.version + 1);
|
|
110
|
+
this.installer.syncIfInstalled(skill.name);
|
|
111
|
+
} else {
|
|
112
|
+
this.store.linkTaskSkill(task.id, skill.id, "applied_to", skill.version);
|
|
113
|
+
}
|
|
114
|
+
} else if (evalResult.confidence < 0.3) {
|
|
115
|
+
// Low confidence means the matched skill is likely unrelated — try creating a new one
|
|
116
|
+
this.ctx.log.info(
|
|
117
|
+
`SkillEvolver: skill "${skill.name}" has low relevance (confidence=${evalResult.confidence}), ` +
|
|
118
|
+
`falling back to new skill evaluation for task "${task.title}"`,
|
|
119
|
+
);
|
|
120
|
+
await this.handleNewSkill(task, chunks);
|
|
121
|
+
} else {
|
|
122
|
+
this.ctx.log.debug(`SkillEvolver: skill "${skill.name}" not worth upgrading (confidence=${evalResult.confidence})`);
|
|
123
|
+
this.markChunksWithSkill(chunks, skill.id);
|
|
124
|
+
this.store.linkTaskSkill(task.id, skill.id, "applied_to", skill.version);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async handleNewSkill(task: Task, chunks: Chunk[]): Promise<void> {
|
|
129
|
+
const minConfidence = this.ctx.config.skillEvolution?.minConfidence ?? DEFAULTS.skillMinConfidence;
|
|
130
|
+
const evalResult = await this.evaluator.evaluateCreate(task);
|
|
131
|
+
|
|
132
|
+
if (evalResult.shouldGenerate && evalResult.confidence >= minConfidence) {
|
|
133
|
+
this.ctx.log.info(`SkillEvolver: generating new skill "${evalResult.suggestedName}" — ${evalResult.reason}`);
|
|
134
|
+
this.store.setTaskSkillMeta(task.id, { skillStatus: "generating", skillReason: evalResult.reason });
|
|
135
|
+
|
|
136
|
+
const skill = await this.generator.generate(task, chunks, evalResult);
|
|
137
|
+
this.markChunksWithSkill(chunks, skill.id);
|
|
138
|
+
this.store.linkTaskSkill(task.id, skill.id, "generated_from", 1);
|
|
139
|
+
this.store.setTaskSkillMeta(task.id, { skillStatus: "generated", skillReason: evalResult.reason });
|
|
140
|
+
|
|
141
|
+
const autoInstall = this.ctx.config.skillEvolution?.autoInstall ?? DEFAULTS.skillAutoInstall;
|
|
142
|
+
if (autoInstall && skill.status === "active") {
|
|
143
|
+
this.installer.install(skill.id);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const reason = evalResult.reason || `confidence不足 (${evalResult.confidence} < ${minConfidence})`;
|
|
147
|
+
this.ctx.log.debug(`SkillEvolver: task "${task.title}" not worth generating skill — ${reason}`);
|
|
148
|
+
this.store.setTaskSkillMeta(task.id, { skillStatus: "not_generated", skillReason: reason });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private markChunksWithSkill(chunks: Chunk[], skillId: string): void {
|
|
153
|
+
for (const chunk of chunks) {
|
|
154
|
+
this.store.setChunkSkillId(chunk.id, skillId);
|
|
155
|
+
}
|
|
156
|
+
this.ctx.log.debug(`SkillEvolver: marked ${chunks.length} chunks with skill_id=${skillId}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private readSkillContent(skill: Skill): string | null {
|
|
160
|
+
const filePath = path.join(skill.dirPath, "SKILL.md");
|
|
161
|
+
try {
|
|
162
|
+
if (fs.existsSync(filePath)) {
|
|
163
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
164
|
+
}
|
|
165
|
+
} catch { /* fall through */ }
|
|
166
|
+
const sv = this.store.getLatestSkillVersion(skill.id);
|
|
167
|
+
return sv?.content ?? null;
|
|
168
|
+
}
|
|
169
|
+
}
|