@memtensor/memos-local-openclaw-plugin 0.3.18 → 0.3.19
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 +21 -11
- package/dist/capture/index.d.ts +1 -1
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +7 -2
- package/dist/capture/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/ingest/dedup.d.ts +2 -2
- package/dist/ingest/dedup.d.ts.map +1 -1
- package/dist/ingest/dedup.js +4 -4
- package/dist/ingest/dedup.js.map +1 -1
- package/dist/ingest/task-processor.d.ts +1 -1
- package/dist/ingest/task-processor.d.ts.map +1 -1
- package/dist/ingest/task-processor.js +14 -13
- package/dist/ingest/task-processor.js.map +1 -1
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +7 -3
- package/dist/ingest/worker.js.map +1 -1
- package/dist/recall/engine.d.ts +5 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +77 -2
- package/dist/recall/engine.js.map +1 -1
- package/dist/skill/evolver.d.ts +2 -1
- package/dist/skill/evolver.d.ts.map +1 -1
- package/dist/skill/evolver.js +2 -2
- package/dist/skill/evolver.js.map +1 -1
- package/dist/skill/generator.d.ts +3 -1
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +15 -1
- package/dist/skill/generator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +24 -8
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +233 -28
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/vector.d.ts +1 -1
- package/dist/storage/vector.d.ts.map +1 -1
- package/dist/storage/vector.js +3 -3
- package/dist/storage/vector.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- 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 +107 -1
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +1 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +52 -3
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +171 -7
- package/package.json +1 -1
- package/skill/browserwing-admin/SKILL.md +521 -0
- package/skill/browserwing-executor/SKILL.md +510 -0
- package/skill/memos-memory-guide/SKILL.md +62 -36
- package/src/capture/index.ts +7 -1
- package/src/index.ts +3 -2
- package/src/ingest/dedup.ts +4 -2
- package/src/ingest/task-processor.ts +14 -13
- package/src/ingest/worker.ts +7 -3
- package/src/recall/engine.ts +94 -4
- package/src/skill/evolver.ts +3 -1
- package/src/skill/generator.ts +15 -0
- package/src/storage/sqlite.ts +262 -34
- package/src/storage/vector.ts +3 -2
- package/src/types.ts +18 -0
- package/src/viewer/html.ts +107 -1
- package/src/viewer/server.ts +48 -3
package/src/capture/index.ts
CHANGED
|
@@ -7,6 +7,10 @@ const SELF_TOOLS = new Set([
|
|
|
7
7
|
"memory_timeline",
|
|
8
8
|
"memory_get",
|
|
9
9
|
"memory_viewer",
|
|
10
|
+
"memory_write_public",
|
|
11
|
+
"skill_search",
|
|
12
|
+
"skill_publish",
|
|
13
|
+
"skill_unpublish",
|
|
10
14
|
]);
|
|
11
15
|
|
|
12
16
|
// OpenClaw inbound metadata sentinels — these are AI-facing prefixes,
|
|
@@ -37,6 +41,7 @@ export function captureMessages(
|
|
|
37
41
|
turnId: string,
|
|
38
42
|
_evidenceTag: string,
|
|
39
43
|
log: Logger,
|
|
44
|
+
owner?: string,
|
|
40
45
|
): ConversationMessage[] {
|
|
41
46
|
const now = Date.now();
|
|
42
47
|
const result: ConversationMessage[] = [];
|
|
@@ -64,10 +69,11 @@ export function captureMessages(
|
|
|
64
69
|
turnId,
|
|
65
70
|
sessionKey,
|
|
66
71
|
toolName: role === "tool" ? msg.toolName : undefined,
|
|
72
|
+
owner: owner ?? "agent:main",
|
|
67
73
|
});
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
log.debug(`Captured ${result.length}/${messages.length} messages for session=${sessionKey} turn=${turnId}`);
|
|
76
|
+
log.debug(`Captured ${result.length}/${messages.length} messages for session=${sessionKey} turn=${turnId} owner=${owner ?? "agent:main"}`);
|
|
71
77
|
return result;
|
|
72
78
|
}
|
|
73
79
|
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { MemosLocalConfig, ToolDefinition, Logger } from "./types";
|
|
|
11
11
|
export interface MemosLocalPlugin {
|
|
12
12
|
id: string;
|
|
13
13
|
tools: ToolDefinition[];
|
|
14
|
-
onConversationTurn: (messages: Array<{ role: string; content: string }>, sessionKey?: string) => void;
|
|
14
|
+
onConversationTurn: (messages: Array<{ role: string; content: string }>, sessionKey?: string, owner?: string) => void;
|
|
15
15
|
/** Wait for all pending ingest operations to complete. */
|
|
16
16
|
flush: () => Promise<void>;
|
|
17
17
|
shutdown: () => void;
|
|
@@ -75,12 +75,13 @@ export function initPlugin(opts: PluginInitOptions = {}): MemosLocalPlugin {
|
|
|
75
75
|
onConversationTurn(
|
|
76
76
|
messages: Array<{ role: string; content: string }>,
|
|
77
77
|
sessionKey?: string,
|
|
78
|
+
owner?: string,
|
|
78
79
|
): void {
|
|
79
80
|
const session = sessionKey ?? "default";
|
|
80
81
|
const turnId = uuid();
|
|
81
82
|
const tag = ctx.config.capture?.evidenceWrapperTag ?? "STORED_MEMORY";
|
|
82
83
|
|
|
83
|
-
const captured = captureMessages(messages, session, turnId, tag, ctx.log);
|
|
84
|
+
const captured = captureMessages(messages, session, turnId, tag, ctx.log, owner);
|
|
84
85
|
if (captured.length > 0) {
|
|
85
86
|
worker.enqueue(captured);
|
|
86
87
|
}
|
package/src/ingest/dedup.ts
CHANGED
|
@@ -14,8 +14,9 @@ export function findDuplicate(
|
|
|
14
14
|
newVec: number[],
|
|
15
15
|
threshold: number,
|
|
16
16
|
log: Logger,
|
|
17
|
+
ownerFilter?: string[],
|
|
17
18
|
): string | null {
|
|
18
|
-
const all = store.getAllEmbeddings();
|
|
19
|
+
const all = store.getAllEmbeddings(ownerFilter);
|
|
19
20
|
|
|
20
21
|
let bestId: string | null = null;
|
|
21
22
|
let bestScore = 0;
|
|
@@ -46,8 +47,9 @@ export function findTopSimilar(
|
|
|
46
47
|
threshold: number,
|
|
47
48
|
topN: number,
|
|
48
49
|
log: Logger,
|
|
50
|
+
ownerFilter?: string[],
|
|
49
51
|
): Array<{ chunkId: string; score: number }> {
|
|
50
|
-
const all = store.getAllEmbeddings();
|
|
52
|
+
const all = store.getAllEmbeddings(ownerFilter);
|
|
51
53
|
const scored: Array<{ chunkId: string; score: number }> = [];
|
|
52
54
|
|
|
53
55
|
for (const { chunkId, vector } of all) {
|
|
@@ -47,15 +47,15 @@ export class TaskProcessor {
|
|
|
47
47
|
* Called after new chunks are ingested.
|
|
48
48
|
* Determines if a new task boundary was crossed and handles transition.
|
|
49
49
|
*/
|
|
50
|
-
async onChunksIngested(sessionKey: string, latestTimestamp: number): Promise<void> {
|
|
51
|
-
this.ctx.log.debug(`TaskProcessor.onChunksIngested called session=${sessionKey} ts=${latestTimestamp} processing=${this.processing}`);
|
|
50
|
+
async onChunksIngested(sessionKey: string, latestTimestamp: number, owner?: string): Promise<void> {
|
|
51
|
+
this.ctx.log.debug(`TaskProcessor.onChunksIngested called session=${sessionKey} ts=${latestTimestamp} owner=${owner ?? "agent:main"} processing=${this.processing}`);
|
|
52
52
|
if (this.processing) {
|
|
53
53
|
this.ctx.log.debug("TaskProcessor.onChunksIngested skipped — already processing");
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
this.processing = true;
|
|
57
57
|
try {
|
|
58
|
-
await this.detectAndProcess(sessionKey, latestTimestamp);
|
|
58
|
+
await this.detectAndProcess(sessionKey, latestTimestamp, owner ?? "agent:main");
|
|
59
59
|
} catch (err) {
|
|
60
60
|
this.ctx.log.error(`TaskProcessor error: ${err}`);
|
|
61
61
|
} finally {
|
|
@@ -63,23 +63,23 @@ export class TaskProcessor {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
private async detectAndProcess(sessionKey: string, latestTimestamp: number): Promise<void> {
|
|
67
|
-
this.ctx.log.debug(`TaskProcessor.detectAndProcess session=${sessionKey}`);
|
|
66
|
+
private async detectAndProcess(sessionKey: string, latestTimestamp: number, owner: string): Promise<void> {
|
|
67
|
+
this.ctx.log.debug(`TaskProcessor.detectAndProcess session=${sessionKey} owner=${owner}`);
|
|
68
68
|
|
|
69
|
-
// Finalize any active tasks from OTHER sessions (session change = task boundary)
|
|
70
|
-
const allActive = this.store.getAllActiveTasks();
|
|
69
|
+
// Finalize any active tasks from OTHER sessions for the SAME owner (session change = task boundary)
|
|
70
|
+
const allActive = this.store.getAllActiveTasks(owner);
|
|
71
71
|
for (const t of allActive) {
|
|
72
72
|
if (t.sessionKey !== sessionKey) {
|
|
73
|
-
this.ctx.log.info(`Session changed: finalizing task=${t.id} from session=${t.sessionKey}`);
|
|
73
|
+
this.ctx.log.info(`Session changed: finalizing task=${t.id} from session=${t.sessionKey} (owner=${owner})`);
|
|
74
74
|
await this.finalizeTask(t);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const activeTask = this.store.getActiveTask(sessionKey);
|
|
79
|
-
this.ctx.log.debug(`TaskProcessor.detectAndProcess activeTask=${activeTask?.id ?? "none"}`);
|
|
78
|
+
const activeTask = this.store.getActiveTask(sessionKey, owner);
|
|
79
|
+
this.ctx.log.debug(`TaskProcessor.detectAndProcess activeTask=${activeTask?.id ?? "none"} owner=${owner}`);
|
|
80
80
|
|
|
81
81
|
if (!activeTask) {
|
|
82
|
-
await this.createNewTask(sessionKey, latestTimestamp);
|
|
82
|
+
await this.createNewTask(sessionKey, latestTimestamp, owner);
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -87,7 +87,7 @@ export class TaskProcessor {
|
|
|
87
87
|
|
|
88
88
|
if (isNewTask) {
|
|
89
89
|
await this.finalizeTask(activeTask);
|
|
90
|
-
await this.createNewTask(sessionKey, latestTimestamp);
|
|
90
|
+
await this.createNewTask(sessionKey, latestTimestamp, owner);
|
|
91
91
|
} else {
|
|
92
92
|
this.assignUnassignedChunks(sessionKey, activeTask.id);
|
|
93
93
|
this.store.updateTask(activeTask.id, { endedAt: undefined });
|
|
@@ -151,7 +151,7 @@ export class TaskProcessor {
|
|
|
151
151
|
.join("\n");
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
private async createNewTask(sessionKey: string, timestamp: number): Promise<void> {
|
|
154
|
+
private async createNewTask(sessionKey: string, timestamp: number, owner: string = "agent:main"): Promise<void> {
|
|
155
155
|
const taskId = uuid();
|
|
156
156
|
const task: Task = {
|
|
157
157
|
id: taskId,
|
|
@@ -159,6 +159,7 @@ export class TaskProcessor {
|
|
|
159
159
|
title: "",
|
|
160
160
|
summary: "",
|
|
161
161
|
status: "active",
|
|
162
|
+
owner,
|
|
162
163
|
startedAt: timestamp,
|
|
163
164
|
endedAt: null,
|
|
164
165
|
updatedAt: timestamp,
|
package/src/ingest/worker.ts
CHANGED
|
@@ -48,6 +48,7 @@ export class IngestWorker {
|
|
|
48
48
|
const t0 = performance.now();
|
|
49
49
|
|
|
50
50
|
let lastSessionKey: string | undefined;
|
|
51
|
+
let lastOwner: string | undefined;
|
|
51
52
|
let lastTimestamp = 0;
|
|
52
53
|
let stored = 0;
|
|
53
54
|
let skipped = 0;
|
|
@@ -64,6 +65,7 @@ export class IngestWorker {
|
|
|
64
65
|
try {
|
|
65
66
|
const result = await this.ingestMessage(msg);
|
|
66
67
|
lastSessionKey = msg.sessionKey;
|
|
68
|
+
lastOwner = msg.owner ?? "agent:main";
|
|
67
69
|
lastTimestamp = Math.max(lastTimestamp, msg.timestamp);
|
|
68
70
|
if (result === "skipped") {
|
|
69
71
|
skipped++;
|
|
@@ -101,9 +103,9 @@ export class IngestWorker {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
if (lastSessionKey) {
|
|
104
|
-
this.ctx.log.debug(`Calling TaskProcessor.onChunksIngested session=${lastSessionKey} ts=${lastTimestamp}`);
|
|
106
|
+
this.ctx.log.debug(`Calling TaskProcessor.onChunksIngested session=${lastSessionKey} ts=${lastTimestamp} owner=${lastOwner}`);
|
|
105
107
|
this.taskProcessor
|
|
106
|
-
.onChunksIngested(lastSessionKey, lastTimestamp)
|
|
108
|
+
.onChunksIngested(lastSessionKey, lastTimestamp, lastOwner)
|
|
107
109
|
.catch((err) => this.ctx.log.error(`TaskProcessor post-ingest error: ${err}`));
|
|
108
110
|
}
|
|
109
111
|
|
|
@@ -147,7 +149,8 @@ export class IngestWorker {
|
|
|
147
149
|
// Smart dedup: find Top-5 similar chunks, then ask LLM to judge
|
|
148
150
|
if (embedding) {
|
|
149
151
|
const similarThreshold = this.ctx.config.dedup?.similarityThreshold ?? 0.75;
|
|
150
|
-
const
|
|
152
|
+
const dedupOwnerFilter = msg.owner ? [msg.owner, "public"] : undefined;
|
|
153
|
+
const topSimilar = findTopSimilar(this.store, embedding, similarThreshold, 5, this.ctx.log, dedupOwnerFilter);
|
|
151
154
|
|
|
152
155
|
if (topSimilar.length > 0) {
|
|
153
156
|
const candidates = topSimilar.map((s, i) => {
|
|
@@ -214,6 +217,7 @@ export class IngestWorker {
|
|
|
214
217
|
embedding: null,
|
|
215
218
|
taskId: null,
|
|
216
219
|
skillId: null,
|
|
220
|
+
owner: msg.owner ?? "agent:main",
|
|
217
221
|
dedupStatus,
|
|
218
222
|
dedupTarget,
|
|
219
223
|
dedupReason,
|
package/src/recall/engine.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { SqliteStore } from "../storage/sqlite";
|
|
2
2
|
import type { Embedder } from "../embedding";
|
|
3
|
-
import type { PluginContext, SearchHit, SearchResult } from "../types";
|
|
4
|
-
import { vectorSearch } from "../storage/vector";
|
|
3
|
+
import type { PluginContext, SearchHit, SearchResult, SkillSearchHit, Skill } from "../types";
|
|
4
|
+
import { vectorSearch, cosineSimilarity } from "../storage/vector";
|
|
5
5
|
import { rrfFuse } from "./rrf";
|
|
6
6
|
import { mmrRerank } from "./mmr";
|
|
7
7
|
import { applyRecencyDecay } from "./recency";
|
|
8
|
+
import { Summarizer } from "../ingest/providers";
|
|
9
|
+
|
|
10
|
+
export type SkillSearchScope = "mix" | "self" | "public";
|
|
8
11
|
|
|
9
12
|
export interface RecallOptions {
|
|
10
13
|
query?: string;
|
|
11
14
|
maxResults?: number;
|
|
12
15
|
minScore?: number;
|
|
13
16
|
role?: string;
|
|
17
|
+
ownerFilter?: string[];
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
const MAX_RECENT_QUERIES = 20;
|
|
@@ -36,10 +40,11 @@ export class RecallEngine {
|
|
|
36
40
|
|
|
37
41
|
const repeatNote = this.checkRepeat(query, maxResults, minScore);
|
|
38
42
|
const candidatePool = maxResults * 5;
|
|
43
|
+
const ownerFilter = opts.ownerFilter;
|
|
39
44
|
|
|
40
45
|
// Step 1: Gather candidates from both FTS and vector search
|
|
41
46
|
const ftsCandidates = query
|
|
42
|
-
? this.store.ftsSearch(query, candidatePool)
|
|
47
|
+
? this.store.ftsSearch(query, candidatePool, ownerFilter)
|
|
43
48
|
: [];
|
|
44
49
|
|
|
45
50
|
let vecCandidates: Array<{ chunkId: string; score: number }> = [];
|
|
@@ -49,7 +54,7 @@ export class RecallEngine {
|
|
|
49
54
|
const maxChunks = recallCfg.vectorSearchMaxChunks && recallCfg.vectorSearchMaxChunks > 0
|
|
50
55
|
? recallCfg.vectorSearchMaxChunks
|
|
51
56
|
: undefined;
|
|
52
|
-
vecCandidates = vectorSearch(this.store, queryVec, candidatePool, maxChunks);
|
|
57
|
+
vecCandidates = vectorSearch(this.store, queryVec, candidatePool, maxChunks, ownerFilter);
|
|
53
58
|
} catch (err) {
|
|
54
59
|
this.ctx.log.warn(`Vector search failed, using FTS only: ${err}`);
|
|
55
60
|
}
|
|
@@ -181,6 +186,91 @@ export class RecallEngine {
|
|
|
181
186
|
this.recentQueries.shift();
|
|
182
187
|
}
|
|
183
188
|
}
|
|
189
|
+
|
|
190
|
+
async searchSkills(query: string, scope: SkillSearchScope, currentOwner: string): Promise<SkillSearchHit[]> {
|
|
191
|
+
const RRF_K = 60;
|
|
192
|
+
const TOP_CANDIDATES = 20;
|
|
193
|
+
|
|
194
|
+
// FTS on name + description
|
|
195
|
+
const ftsCandidates = this.store.skillFtsSearch(query, TOP_CANDIDATES, scope, currentOwner);
|
|
196
|
+
|
|
197
|
+
// Vector search on description embedding
|
|
198
|
+
let vecCandidates: Array<{ skillId: string; score: number }> = [];
|
|
199
|
+
try {
|
|
200
|
+
const queryVec = await this.embedder.embedQuery(query);
|
|
201
|
+
const allEmb = this.store.getSkillEmbeddings(scope, currentOwner);
|
|
202
|
+
vecCandidates = allEmb.map((row) => ({
|
|
203
|
+
skillId: row.skillId,
|
|
204
|
+
score: cosineSimilarity(queryVec, row.vector),
|
|
205
|
+
}));
|
|
206
|
+
vecCandidates.sort((a, b) => b.score - a.score);
|
|
207
|
+
vecCandidates = vecCandidates.slice(0, TOP_CANDIDATES);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
this.ctx.log.warn(`Skill vector search failed, using FTS only: ${err}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// RRF fusion
|
|
213
|
+
const ftsRanked = ftsCandidates.map((c) => ({ id: c.skillId, score: c.score }));
|
|
214
|
+
const vecRanked = vecCandidates.map((c) => ({ id: c.skillId, score: c.score }));
|
|
215
|
+
const rrfScores = rrfFuse([ftsRanked, vecRanked], RRF_K);
|
|
216
|
+
|
|
217
|
+
if (rrfScores.size === 0) return [];
|
|
218
|
+
|
|
219
|
+
const sorted = [...rrfScores.entries()]
|
|
220
|
+
.map(([id, score]) => ({ id, score }))
|
|
221
|
+
.sort((a, b) => b.score - a.score)
|
|
222
|
+
.slice(0, TOP_CANDIDATES);
|
|
223
|
+
|
|
224
|
+
// Load skill details for LLM judgment
|
|
225
|
+
const candidateSkills: Array<{ skill: Skill; rrfScore: number }> = [];
|
|
226
|
+
for (const item of sorted) {
|
|
227
|
+
const skill = this.store.getSkill(item.id);
|
|
228
|
+
if (skill) candidateSkills.push({ skill, rrfScore: item.score });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (candidateSkills.length === 0) return [];
|
|
232
|
+
|
|
233
|
+
// LLM relevance judgment
|
|
234
|
+
const summarizer = new Summarizer(this.ctx.config.summarizer, this.ctx.log);
|
|
235
|
+
const relevantIndices = await this.judgeSkillRelevance(summarizer, query, candidateSkills);
|
|
236
|
+
|
|
237
|
+
return relevantIndices.map((idx) => {
|
|
238
|
+
const { skill, rrfScore } = candidateSkills[idx];
|
|
239
|
+
return {
|
|
240
|
+
skillId: skill.id,
|
|
241
|
+
name: skill.name,
|
|
242
|
+
description: skill.description,
|
|
243
|
+
owner: skill.owner,
|
|
244
|
+
visibility: skill.visibility,
|
|
245
|
+
score: rrfScore,
|
|
246
|
+
reason: "relevant",
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async judgeSkillRelevance(
|
|
252
|
+
summarizer: Summarizer,
|
|
253
|
+
query: string,
|
|
254
|
+
candidates: Array<{ skill: Skill; rrfScore: number }>,
|
|
255
|
+
): Promise<number[]> {
|
|
256
|
+
const candidateList = candidates.map((c, i) => ({
|
|
257
|
+
index: i,
|
|
258
|
+
summary: `[${c.skill.name}] ${c.skill.description}`,
|
|
259
|
+
role: "skill" as const,
|
|
260
|
+
}));
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const result = await summarizer.filterRelevant(query, candidateList);
|
|
264
|
+
if (result && result.relevant.length > 0) {
|
|
265
|
+
return result.relevant.map((r) => r);
|
|
266
|
+
}
|
|
267
|
+
} catch (err) {
|
|
268
|
+
this.ctx.log.warn(`Skill relevance judgment failed, returning all: ${err}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Fallback: return all candidates
|
|
272
|
+
return candidates.map((_, i) => i);
|
|
273
|
+
}
|
|
184
274
|
}
|
|
185
275
|
|
|
186
276
|
function makeExcerpt(content: string): string {
|
package/src/skill/evolver.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import type { SqliteStore } from "../storage/sqlite";
|
|
4
4
|
import type { RecallEngine } from "../recall/engine";
|
|
5
|
+
import type { Embedder } from "../embedding";
|
|
5
6
|
import type { Task, Skill, Chunk, PluginContext } from "../types";
|
|
6
7
|
import { DEFAULTS } from "../types";
|
|
7
8
|
import { SkillEvaluator } from "./evaluator";
|
|
@@ -20,9 +21,10 @@ export class SkillEvolver {
|
|
|
20
21
|
private store: SqliteStore,
|
|
21
22
|
private engine: RecallEngine,
|
|
22
23
|
private ctx: PluginContext,
|
|
24
|
+
embedder?: Embedder,
|
|
23
25
|
) {
|
|
24
26
|
this.evaluator = new SkillEvaluator(ctx);
|
|
25
|
-
this.generator = new SkillGenerator(store, engine, ctx);
|
|
27
|
+
this.generator = new SkillGenerator(store, engine, ctx, embedder);
|
|
26
28
|
this.upgrader = new SkillUpgrader(store, ctx);
|
|
27
29
|
this.installer = new SkillInstaller(store, ctx);
|
|
28
30
|
}
|
package/src/skill/generator.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import type { SqliteStore } from "../storage/sqlite";
|
|
5
5
|
import type { RecallEngine } from "../recall/engine";
|
|
6
|
+
import type { Embedder } from "../embedding";
|
|
6
7
|
import type { Chunk, Task, Skill, PluginContext, SummarizerConfig, SkillGenerateOutput } from "../types";
|
|
7
8
|
import { DEFAULTS } from "../types";
|
|
8
9
|
import type { CreateEvalResult } from "./evaluator";
|
|
@@ -176,13 +177,16 @@ If no references should be extracted, reply with: []`;
|
|
|
176
177
|
|
|
177
178
|
export class SkillGenerator {
|
|
178
179
|
private validator: SkillValidator;
|
|
180
|
+
private embedder: Embedder | null = null;
|
|
179
181
|
|
|
180
182
|
constructor(
|
|
181
183
|
private store: SqliteStore,
|
|
182
184
|
private engine: RecallEngine,
|
|
183
185
|
private ctx: PluginContext,
|
|
186
|
+
embedder?: Embedder,
|
|
184
187
|
) {
|
|
185
188
|
this.validator = new SkillValidator(ctx);
|
|
189
|
+
this.embedder = embedder ?? null;
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
async generate(task: Task, chunks: Chunk[], evalResult: CreateEvalResult): Promise<Skill> {
|
|
@@ -270,12 +274,23 @@ export class SkillGenerator {
|
|
|
270
274
|
sourceType: "task",
|
|
271
275
|
dirPath,
|
|
272
276
|
installed: 0,
|
|
277
|
+
owner: "agent:main",
|
|
278
|
+
visibility: "private",
|
|
273
279
|
qualityScore: validation.qualityScore,
|
|
274
280
|
createdAt: now,
|
|
275
281
|
updatedAt: now,
|
|
276
282
|
};
|
|
277
283
|
this.store.insertSkill(skill);
|
|
278
284
|
|
|
285
|
+
if (description && this.embedder) {
|
|
286
|
+
try {
|
|
287
|
+
const [descEmb] = await this.embedder.embed([description]);
|
|
288
|
+
if (descEmb) this.store.upsertSkillEmbedding(skillId, descEmb);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
this.ctx.log.warn(`SkillGenerator: embedding for description failed: ${err}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
279
294
|
this.store.insertSkillVersion({
|
|
280
295
|
id: uuid(),
|
|
281
296
|
skillId,
|