@memtensor/memos-local-openclaw-plugin 0.3.20 → 1.0.1
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 +239 -22
- package/dist/capture/index.d.ts +1 -1
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +33 -8
- 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 -1
- package/dist/index.js.map +1 -1
- package/dist/ingest/providers/anthropic.d.ts.map +1 -1
- package/dist/ingest/providers/anthropic.js +22 -8
- package/dist/ingest/providers/anthropic.js.map +1 -1
- package/dist/ingest/providers/bedrock.d.ts.map +1 -1
- package/dist/ingest/providers/bedrock.js +22 -8
- package/dist/ingest/providers/bedrock.js.map +1 -1
- package/dist/ingest/providers/gemini.d.ts.map +1 -1
- package/dist/ingest/providers/gemini.js +22 -8
- package/dist/ingest/providers/gemini.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +13 -18
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +213 -139
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +1 -1
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +37 -17
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.d.ts +28 -3
- package/dist/ingest/task-processor.d.ts.map +1 -1
- package/dist/ingest/task-processor.js +166 -67
- package/dist/ingest/task-processor.js.map +1 -1
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +97 -75
- package/dist/ingest/worker.js.map +1 -1
- package/dist/shared/llm-call.d.ts +26 -0
- package/dist/shared/llm-call.d.ts.map +1 -0
- package/dist/shared/llm-call.js +163 -0
- package/dist/shared/llm-call.js.map +1 -0
- package/dist/skill/evaluator.d.ts +0 -3
- package/dist/skill/evaluator.d.ts.map +1 -1
- package/dist/skill/evaluator.js +34 -59
- package/dist/skill/evaluator.js.map +1 -1
- package/dist/skill/evolver.d.ts +22 -1
- package/dist/skill/evolver.d.ts.map +1 -1
- package/dist/skill/evolver.js +191 -32
- package/dist/skill/evolver.js.map +1 -1
- package/dist/skill/generator.d.ts +0 -3
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +15 -50
- package/dist/skill/generator.js.map +1 -1
- package/dist/skill/upgrader.d.ts +0 -2
- package/dist/skill/upgrader.d.ts.map +1 -1
- package/dist/skill/upgrader.js +4 -39
- package/dist/skill/upgrader.js.map +1 -1
- package/dist/skill/validator.d.ts +0 -2
- package/dist/skill/validator.d.ts.map +1 -1
- package/dist/skill/validator.js +14 -44
- package/dist/skill/validator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +13 -2
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +92 -15
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/memory-get.d.ts.map +1 -1
- package/dist/tools/memory-get.js +5 -1
- package/dist/tools/memory-get.js.map +1 -1
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +5 -0
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/tools/memory-timeline.d.ts.map +1 -1
- package/dist/tools/memory-timeline.js +11 -2
- package/dist/tools/memory-timeline.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +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 +380 -26
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +9 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +549 -184
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +9 -3
- package/package.json +2 -1
- package/src/capture/index.ts +39 -10
- package/src/index.ts +3 -2
- package/src/ingest/providers/anthropic.ts +22 -8
- package/src/ingest/providers/bedrock.ts +22 -8
- package/src/ingest/providers/gemini.ts +22 -8
- package/src/ingest/providers/index.ts +192 -142
- package/src/ingest/providers/openai.ts +37 -17
- package/src/ingest/task-processor.ts +183 -65
- package/src/ingest/worker.ts +98 -77
- package/src/shared/llm-call.ts +144 -0
- package/src/skill/evaluator.ts +35 -64
- package/src/skill/evolver.ts +201 -33
- package/src/skill/generator.ts +16 -59
- package/src/skill/upgrader.ts +5 -43
- package/src/skill/validator.ts +15 -47
- package/src/storage/sqlite.ts +107 -15
- package/src/tools/memory-get.ts +6 -1
- package/src/tools/memory-search.ts +6 -0
- package/src/tools/memory-timeline.ts +13 -1
- package/src/types.ts +2 -1
- package/src/viewer/html.ts +380 -26
- package/src/viewer/server.ts +535 -197
package/src/skill/generator.ts
CHANGED
|
@@ -4,10 +4,11 @@ import * as path from "path";
|
|
|
4
4
|
import type { SqliteStore } from "../storage/sqlite";
|
|
5
5
|
import type { RecallEngine } from "../recall/engine";
|
|
6
6
|
import type { Embedder } from "../embedding";
|
|
7
|
-
import type { Chunk, Task, Skill, PluginContext,
|
|
7
|
+
import type { Chunk, Task, Skill, PluginContext, SkillGenerateOutput } from "../types";
|
|
8
8
|
import { DEFAULTS } from "../types";
|
|
9
9
|
import type { CreateEvalResult } from "./evaluator";
|
|
10
10
|
import { SkillValidator } from "./validator";
|
|
11
|
+
import { buildSkillConfigChain, callLLMWithFallback } from "../shared/llm-call";
|
|
11
12
|
|
|
12
13
|
// ─── Step 1: Generate SKILL.md ───
|
|
13
14
|
// Based on Anthropic skill-creator principles:
|
|
@@ -138,6 +139,7 @@ Requirements:
|
|
|
138
139
|
- Mix formal and casual tones, include some with typos or shorthand
|
|
139
140
|
- Each prompt should be complex enough that the agent would need the skill (not simple Q&A)
|
|
140
141
|
- Write expectations that are specific and verifiable
|
|
142
|
+
- LANGUAGE RULE: Write prompts and expectations in the SAME language as the skill content. If the skill is in Chinese, write Chinese test prompts. If English, write English.
|
|
141
143
|
|
|
142
144
|
Skill:
|
|
143
145
|
{SKILL_CONTENT}
|
|
@@ -161,6 +163,7 @@ Rules:
|
|
|
161
163
|
- Each reference should be a standalone markdown document.
|
|
162
164
|
- Don't duplicate what's already in SKILL.md — references are for deeper detail.
|
|
163
165
|
- If there's nothing worth extracting, return an empty array.
|
|
166
|
+
- LANGUAGE RULE: Write reference content in the SAME language as the SKILL.md and task record. Chinese input → Chinese output.
|
|
164
167
|
|
|
165
168
|
SKILL.md:
|
|
166
169
|
{SKILL_CONTENT}
|
|
@@ -340,8 +343,8 @@ export class SkillGenerator {
|
|
|
340
343
|
}
|
|
341
344
|
|
|
342
345
|
private async step1GenerateSkillMd(task: Task, conversationText: string, evalResult: CreateEvalResult): Promise<string> {
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
346
|
+
const chain = buildSkillConfigChain(this.ctx);
|
|
347
|
+
if (chain.length === 0) throw new Error("No LLM configured for skill generation");
|
|
345
348
|
|
|
346
349
|
const lang = this.detectLanguage(conversationText);
|
|
347
350
|
const langInstruction = `\n\n⚠️ LANGUAGE REQUIREMENT: The task record is in ${lang}. You MUST write ALL prose content (description, headings, explanations, pitfalls) in ${lang}. Only the "name" field stays in English kebab-case.\n`;
|
|
@@ -353,7 +356,7 @@ export class SkillGenerator {
|
|
|
353
356
|
.replace("{CONVERSATION}", conversationText.slice(0, 12000))
|
|
354
357
|
+ langInstruction;
|
|
355
358
|
|
|
356
|
-
const raw = await
|
|
359
|
+
const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillGenerator.step1", { maxTokens: 6000, temperature: 0.2, timeoutMs: 120_000 });
|
|
357
360
|
|
|
358
361
|
const trimmed = raw.trim();
|
|
359
362
|
if (trimmed.startsWith("---")) return trimmed;
|
|
@@ -368,15 +371,15 @@ export class SkillGenerator {
|
|
|
368
371
|
skillContent: string,
|
|
369
372
|
conversationText: string,
|
|
370
373
|
): Promise<Array<{ filename: string; content: string }>> {
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
374
|
+
const chain = buildSkillConfigChain(this.ctx);
|
|
375
|
+
if (chain.length === 0) return [];
|
|
373
376
|
|
|
374
377
|
const prompt = STEP2_SCRIPTS_PROMPT
|
|
375
378
|
.replace("{SKILL_CONTENT}", skillContent.slice(0, 4000))
|
|
376
379
|
.replace("{CONVERSATION}", conversationText.slice(0, 6000));
|
|
377
380
|
|
|
378
381
|
try {
|
|
379
|
-
const raw = await
|
|
382
|
+
const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillGenerator.scripts", { maxTokens: 3000, temperature: 0.1, timeoutMs: 120_000 });
|
|
380
383
|
return this.parseJSONArray<{ filename: string; content: string }>(raw);
|
|
381
384
|
} catch (err) {
|
|
382
385
|
this.ctx.log.warn(`SkillGenerator: script extraction failed: ${err}`);
|
|
@@ -390,15 +393,15 @@ export class SkillGenerator {
|
|
|
390
393
|
skillContent: string,
|
|
391
394
|
conversationText: string,
|
|
392
395
|
): Promise<Array<{ filename: string; content: string }>> {
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
396
|
+
const chain = buildSkillConfigChain(this.ctx);
|
|
397
|
+
if (chain.length === 0) return [];
|
|
395
398
|
|
|
396
399
|
const prompt = STEP2B_REFS_PROMPT
|
|
397
400
|
.replace("{SKILL_CONTENT}", skillContent.slice(0, 4000))
|
|
398
401
|
.replace("{CONVERSATION}", conversationText.slice(0, 6000));
|
|
399
402
|
|
|
400
403
|
try {
|
|
401
|
-
const raw = await
|
|
404
|
+
const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillGenerator.refs", { maxTokens: 3000, temperature: 0.1, timeoutMs: 120_000 });
|
|
402
405
|
return this.parseJSONArray<{ filename: string; content: string }>(raw);
|
|
403
406
|
} catch (err) {
|
|
404
407
|
this.ctx.log.warn(`SkillGenerator: reference extraction failed: ${err}`);
|
|
@@ -411,8 +414,8 @@ export class SkillGenerator {
|
|
|
411
414
|
private async step3GenerateEvals(
|
|
412
415
|
skillContent: string,
|
|
413
416
|
): Promise<Array<{ id: number; prompt: string; expectations: string[]; trigger_confidence?: string }>> {
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
417
|
+
const chain = buildSkillConfigChain(this.ctx);
|
|
418
|
+
if (chain.length === 0) return [];
|
|
416
419
|
|
|
417
420
|
const lang = this.detectLanguage(skillContent);
|
|
418
421
|
const prompt = STEP3_EVALS_PROMPT
|
|
@@ -420,7 +423,7 @@ export class SkillGenerator {
|
|
|
420
423
|
+ `\n\n⚠️ LANGUAGE: Write test prompts and expectations in ${lang}, matching the skill's language.\n`;
|
|
421
424
|
|
|
422
425
|
try {
|
|
423
|
-
const raw = await
|
|
426
|
+
const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillGenerator.evals", { maxTokens: 2000, temperature: 0.3, timeoutMs: 120_000 });
|
|
424
427
|
return this.parseJSONArray(raw);
|
|
425
428
|
} catch (err) {
|
|
426
429
|
this.ctx.log.warn(`SkillGenerator: eval generation failed: ${err}`);
|
|
@@ -464,42 +467,6 @@ export class SkillGenerator {
|
|
|
464
467
|
return { hitCount, results };
|
|
465
468
|
}
|
|
466
469
|
|
|
467
|
-
// ─── Shared LLM call ───
|
|
468
|
-
|
|
469
|
-
private async callLLM(
|
|
470
|
-
cfg: SummarizerConfig,
|
|
471
|
-
prompt: string,
|
|
472
|
-
opts: { maxTokens: number; temperature: number },
|
|
473
|
-
): Promise<string> {
|
|
474
|
-
const endpoint = this.normalizeEndpoint(cfg.endpoint ?? "https://api.openai.com/v1/chat/completions");
|
|
475
|
-
const model = cfg.model ?? "gpt-4o-mini";
|
|
476
|
-
const headers: Record<string, string> = {
|
|
477
|
-
"Content-Type": "application/json",
|
|
478
|
-
Authorization: `Bearer ${cfg.apiKey}`,
|
|
479
|
-
...cfg.headers,
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
const resp = await fetch(endpoint, {
|
|
483
|
-
method: "POST",
|
|
484
|
-
headers,
|
|
485
|
-
body: JSON.stringify({
|
|
486
|
-
model,
|
|
487
|
-
temperature: opts.temperature,
|
|
488
|
-
max_tokens: opts.maxTokens,
|
|
489
|
-
messages: [{ role: "user", content: prompt }],
|
|
490
|
-
}),
|
|
491
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ?? 120_000),
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
if (!resp.ok) {
|
|
495
|
-
const body = await resp.text();
|
|
496
|
-
throw new Error(`LLM call failed (${resp.status}): ${body}`);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const json = (await resp.json()) as { choices: Array<{ message: { content: string } }> };
|
|
500
|
-
return json.choices[0]?.message?.content?.trim() ?? "";
|
|
501
|
-
}
|
|
502
|
-
|
|
503
470
|
// ─── Helpers ───
|
|
504
471
|
|
|
505
472
|
private parseJSONArray<T>(raw: string): T[] {
|
|
@@ -532,14 +499,4 @@ export class SkillGenerator {
|
|
|
532
499
|
return "";
|
|
533
500
|
}
|
|
534
501
|
|
|
535
|
-
private getProviderConfig(): SummarizerConfig | undefined {
|
|
536
|
-
return this.ctx.config.summarizer;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
private normalizeEndpoint(url: string): string {
|
|
540
|
-
const stripped = url.replace(/\/+$/, "");
|
|
541
|
-
if (stripped.endsWith("/chat/completions")) return stripped;
|
|
542
|
-
if (stripped.endsWith("/completions")) return stripped;
|
|
543
|
-
return `${stripped}/chat/completions`;
|
|
544
|
-
}
|
|
545
502
|
}
|
package/src/skill/upgrader.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { v4 as uuid } from "uuid";
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import type { SqliteStore } from "../storage/sqlite";
|
|
5
|
-
import type { Task, Skill, PluginContext
|
|
5
|
+
import type { Task, Skill, PluginContext } from "../types";
|
|
6
6
|
import type { UpgradeEvalResult } from "./evaluator";
|
|
7
7
|
import { SkillValidator } from "./validator";
|
|
8
|
+
import { buildSkillConfigChain, callLLMWithFallback } from "../shared/llm-call";
|
|
8
9
|
|
|
9
10
|
const UPGRADE_PROMPT = `You are a Skill upgrade expert. You're merging new real-world execution experience into an existing Skill to make it better.
|
|
10
11
|
|
|
@@ -163,8 +164,8 @@ export class SkillUpgrader {
|
|
|
163
164
|
currentContent: string,
|
|
164
165
|
evalResult: UpgradeEvalResult,
|
|
165
166
|
): Promise<{ newContent: string; changelog: string; changeSummary: string }> {
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
167
|
+
const chain = buildSkillConfigChain(this.ctx);
|
|
168
|
+
if (chain.length === 0) throw new Error("No LLM configured for skill upgrade");
|
|
168
169
|
|
|
169
170
|
const newVersion = skill.version + 1;
|
|
170
171
|
|
|
@@ -189,35 +190,7 @@ export class SkillUpgrader {
|
|
|
189
190
|
.replace("{TASK_ID}", task.id)
|
|
190
191
|
+ langInstruction;
|
|
191
192
|
|
|
192
|
-
const
|
|
193
|
-
const model = cfg.model ?? "gpt-4o-mini";
|
|
194
|
-
const headers: Record<string, string> = {
|
|
195
|
-
"Content-Type": "application/json",
|
|
196
|
-
Authorization: `Bearer ${cfg.apiKey}`,
|
|
197
|
-
...cfg.headers,
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const resp = await fetch(endpoint, {
|
|
201
|
-
method: "POST",
|
|
202
|
-
headers,
|
|
203
|
-
body: JSON.stringify({
|
|
204
|
-
model,
|
|
205
|
-
temperature: cfg.temperature ?? 0.2,
|
|
206
|
-
max_tokens: 6000,
|
|
207
|
-
messages: [
|
|
208
|
-
{ role: "user", content: prompt },
|
|
209
|
-
],
|
|
210
|
-
}),
|
|
211
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ?? 90_000),
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
if (!resp.ok) {
|
|
215
|
-
const body = await resp.text();
|
|
216
|
-
throw new Error(`Skill upgrade LLM failed (${resp.status}): ${body}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const json = (await resp.json()) as { choices: Array<{ message: { content: string } }> };
|
|
220
|
-
const raw = json.choices[0]?.message?.content?.trim() ?? "";
|
|
193
|
+
const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillUpgrader.upgrade", { maxTokens: 6000, temperature: 0.2, timeoutMs: 90_000 });
|
|
221
194
|
|
|
222
195
|
const changelogSep = raw.indexOf("---CHANGELOG---");
|
|
223
196
|
if (changelogSep !== -1) {
|
|
@@ -243,15 +216,4 @@ export class SkillUpgrader {
|
|
|
243
216
|
if (match2) return match2[1];
|
|
244
217
|
return "";
|
|
245
218
|
}
|
|
246
|
-
|
|
247
|
-
private getProviderConfig(): SummarizerConfig | undefined {
|
|
248
|
-
return this.ctx.config.summarizer;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private normalizeEndpoint(url: string): string {
|
|
252
|
-
const stripped = url.replace(/\/+$/, "");
|
|
253
|
-
if (stripped.endsWith("/chat/completions")) return stripped;
|
|
254
|
-
if (stripped.endsWith("/completions")) return stripped;
|
|
255
|
-
return `${stripped}/chat/completions`;
|
|
256
|
-
}
|
|
257
219
|
}
|
package/src/skill/validator.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import type { PluginContext
|
|
3
|
+
import type { PluginContext } from "../types";
|
|
4
4
|
import { DEFAULTS } from "../types";
|
|
5
|
+
import { buildSkillConfigChain, callLLMWithFallback } from "../shared/llm-call";
|
|
5
6
|
|
|
6
7
|
export interface ValidationResult {
|
|
7
8
|
valid: boolean;
|
|
@@ -133,44 +134,20 @@ export class SkillValidator {
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
private async assessQuality(dirPath: string, result: ValidationResult): Promise<void> {
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
137
|
+
const chain = buildSkillConfigChain(this.ctx);
|
|
138
|
+
if (chain.length === 0) return;
|
|
138
139
|
|
|
139
140
|
const skillMdPath = path.join(dirPath, "SKILL.md");
|
|
140
141
|
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
141
142
|
|
|
142
143
|
const prompt = QUALITY_PROMPT.replace("{SKILL_CONTENT}", content.slice(0, 6000));
|
|
143
|
-
const endpoint = this.normalizeEndpoint(cfg.endpoint ?? "https://api.openai.com/v1/chat/completions");
|
|
144
|
-
const headers: Record<string, string> = {
|
|
145
|
-
"Content-Type": "application/json",
|
|
146
|
-
Authorization: `Bearer ${cfg.apiKey}`,
|
|
147
|
-
...cfg.headers,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const resp = await fetch(endpoint, {
|
|
151
|
-
method: "POST",
|
|
152
|
-
headers,
|
|
153
|
-
body: JSON.stringify({
|
|
154
|
-
model: cfg.model ?? "gpt-4o-mini",
|
|
155
|
-
temperature: cfg.temperature ?? 0.1,
|
|
156
|
-
max_tokens: 1024,
|
|
157
|
-
messages: [{ role: "user", content: prompt }],
|
|
158
|
-
}),
|
|
159
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ?? 30_000),
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (!resp.ok) {
|
|
163
|
-
const body = await resp.text();
|
|
164
|
-
throw new Error(`Quality LLM failed (${resp.status}): ${body}`);
|
|
165
|
-
}
|
|
166
144
|
|
|
167
|
-
|
|
168
|
-
|
|
145
|
+
try {
|
|
146
|
+
const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "SkillValidator.quality");
|
|
169
147
|
|
|
170
|
-
|
|
171
|
-
|
|
148
|
+
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
149
|
+
if (!jsonMatch) return;
|
|
172
150
|
|
|
173
|
-
try {
|
|
174
151
|
const assessment = JSON.parse(jsonMatch[0]) as {
|
|
175
152
|
score: number;
|
|
176
153
|
strengths: string[];
|
|
@@ -189,21 +166,10 @@ export class SkillValidator {
|
|
|
189
166
|
if (result.qualityScore < 6) {
|
|
190
167
|
result.warnings.push(`Quality score ${result.qualityScore}/10 is below threshold, marked as draft`);
|
|
191
168
|
}
|
|
192
|
-
} catch {
|
|
193
|
-
this.ctx.log.warn(
|
|
169
|
+
} catch (err) {
|
|
170
|
+
this.ctx.log.warn(`SkillValidator: quality assessment failed: ${err}`);
|
|
194
171
|
}
|
|
195
172
|
}
|
|
196
|
-
|
|
197
|
-
private getProviderConfig(): SummarizerConfig | undefined {
|
|
198
|
-
return this.ctx.config.summarizer;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
private normalizeEndpoint(url: string): string {
|
|
202
|
-
const stripped = url.replace(/\/+$/, "");
|
|
203
|
-
if (stripped.endsWith("/chat/completions")) return stripped;
|
|
204
|
-
if (stripped.endsWith("/completions")) return stripped;
|
|
205
|
-
return `${stripped}/chat/completions`;
|
|
206
|
-
}
|
|
207
173
|
}
|
|
208
174
|
|
|
209
175
|
const QUALITY_PROMPT = `You are a skill quality reviewer. Evaluate the following SKILL.md and give a score from 0 to 10.
|
|
@@ -218,10 +184,12 @@ Criteria:
|
|
|
218
184
|
SKILL.md:
|
|
219
185
|
{SKILL_CONTENT}
|
|
220
186
|
|
|
187
|
+
LANGUAGE RULE: "strengths", "weaknesses", and "suggestions" MUST use the SAME language as the SKILL.md content. Chinese skill → Chinese feedback. English skill → English feedback.
|
|
188
|
+
|
|
221
189
|
Reply in JSON only:
|
|
222
190
|
{
|
|
223
191
|
"score": 0-10,
|
|
224
|
-
"strengths": ["what's good"],
|
|
225
|
-
"weaknesses": ["what's lacking"],
|
|
226
|
-
"suggestions": ["how to improve"]
|
|
192
|
+
"strengths": ["what's good (same language as skill)"],
|
|
193
|
+
"weaknesses": ["what's lacking (same language as skill)"],
|
|
194
|
+
"suggestions": ["how to improve (same language as skill)"]
|
|
227
195
|
}`;
|
package/src/storage/sqlite.ts
CHANGED
|
@@ -306,6 +306,14 @@ export class SqliteStore {
|
|
|
306
306
|
.run(meta.skillStatus, meta.skillReason, Date.now(), taskId);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
getTasksBySkillStatus(statuses: string[]): Task[] {
|
|
310
|
+
const placeholders = statuses.map(() => "?").join(",");
|
|
311
|
+
const rows = this.db.prepare(
|
|
312
|
+
`SELECT * FROM tasks WHERE skill_status IN (${placeholders}) AND status = 'completed' ORDER BY updated_at ASC`,
|
|
313
|
+
).all(...statuses) as TaskRow[];
|
|
314
|
+
return rows.map(rowToTask);
|
|
315
|
+
}
|
|
316
|
+
|
|
309
317
|
private migrateMergeFields(): void {
|
|
310
318
|
const cols = this.db.prepare("PRAGMA table_info(chunks)").all() as Array<{ name: string }>;
|
|
311
319
|
if (!cols.some((c) => c.name === "merge_count")) {
|
|
@@ -633,6 +641,10 @@ export class SqliteStore {
|
|
|
633
641
|
`).run(chunkId, buf, vector.length, Date.now());
|
|
634
642
|
}
|
|
635
643
|
|
|
644
|
+
deleteEmbedding(chunkId: string): void {
|
|
645
|
+
this.db.prepare("DELETE FROM embeddings WHERE chunk_id = ?").run(chunkId);
|
|
646
|
+
}
|
|
647
|
+
|
|
636
648
|
// ─── Read ───
|
|
637
649
|
|
|
638
650
|
getChunk(chunkId: string): Chunk | null {
|
|
@@ -640,16 +652,37 @@ export class SqliteStore {
|
|
|
640
652
|
return row ? rowToChunk(row) : null;
|
|
641
653
|
}
|
|
642
654
|
|
|
643
|
-
|
|
644
|
-
return this.getChunk(
|
|
655
|
+
getChunkForOwners(chunkId: string, ownerFilter?: string[]): Chunk | null {
|
|
656
|
+
if (!ownerFilter || ownerFilter.length === 0) return this.getChunk(chunkId);
|
|
657
|
+
|
|
658
|
+
const placeholders = ownerFilter.map(() => "?").join(",");
|
|
659
|
+
const row = this.db.prepare(
|
|
660
|
+
`SELECT * FROM chunks WHERE id = ? AND owner IN (${placeholders}) LIMIT 1`,
|
|
661
|
+
).get(chunkId, ...ownerFilter) as ChunkRow | undefined;
|
|
662
|
+
return row ? rowToChunk(row) : null;
|
|
645
663
|
}
|
|
646
664
|
|
|
647
|
-
|
|
648
|
-
|
|
665
|
+
getChunksByRef(ref: ChunkRef, ownerFilter?: string[]): Chunk | null {
|
|
666
|
+
return this.getChunkForOwners(ref.chunkId, ownerFilter);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
getNeighborChunks(sessionKey: string, turnId: string, seq: number, window: number, ownerFilter?: string[]): Chunk[] {
|
|
670
|
+
let sql = `
|
|
649
671
|
SELECT * FROM chunks
|
|
650
|
-
WHERE session_key =
|
|
672
|
+
WHERE session_key = ?`;
|
|
673
|
+
const params: any[] = [sessionKey];
|
|
674
|
+
|
|
675
|
+
if (ownerFilter && ownerFilter.length > 0) {
|
|
676
|
+
const placeholders = ownerFilter.map(() => "?").join(",");
|
|
677
|
+
sql += ` AND owner IN (${placeholders})`;
|
|
678
|
+
params.push(...ownerFilter);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
sql += `
|
|
651
682
|
ORDER BY created_at, seq
|
|
652
|
-
|
|
683
|
+
`;
|
|
684
|
+
|
|
685
|
+
const allRows = this.db.prepare(sql).all(...params) as ChunkRow[];
|
|
653
686
|
|
|
654
687
|
const targetIdx = allRows.findIndex(
|
|
655
688
|
(r) => r.turn_id === turnId && r.seq === seq,
|
|
@@ -840,20 +873,46 @@ export class SqliteStore {
|
|
|
840
873
|
|
|
841
874
|
deleteAll(): number {
|
|
842
875
|
this.db.exec("PRAGMA foreign_keys = OFF");
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
876
|
+
const tables = [
|
|
877
|
+
"task_skills",
|
|
878
|
+
"skill_embeddings",
|
|
879
|
+
"skill_versions",
|
|
880
|
+
"skills",
|
|
881
|
+
"embeddings",
|
|
882
|
+
"chunks",
|
|
883
|
+
"tasks",
|
|
884
|
+
"viewer_events",
|
|
885
|
+
"api_logs",
|
|
886
|
+
"tool_calls",
|
|
887
|
+
];
|
|
888
|
+
for (const table of tables) {
|
|
889
|
+
try {
|
|
890
|
+
this.db.prepare(`DELETE FROM ${table}`).run();
|
|
891
|
+
} catch (err) {
|
|
892
|
+
this.log.warn(`deleteAll: failed to clear ${table}: ${err}`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
852
895
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
853
896
|
const remaining = this.countChunks();
|
|
854
897
|
return remaining === 0 ? 1 : 0;
|
|
855
898
|
}
|
|
856
899
|
|
|
900
|
+
deleteTask(taskId: string): boolean {
|
|
901
|
+
this.db.prepare("DELETE FROM task_skills WHERE task_id = ?").run(taskId);
|
|
902
|
+
this.db.prepare("UPDATE chunks SET task_id = NULL WHERE task_id = ?").run(taskId);
|
|
903
|
+
const result = this.db.prepare("DELETE FROM tasks WHERE id = ?").run(taskId);
|
|
904
|
+
return result.changes > 0;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
deleteSkill(skillId: string): boolean {
|
|
908
|
+
this.db.prepare("DELETE FROM task_skills WHERE skill_id = ?").run(skillId);
|
|
909
|
+
this.db.prepare("DELETE FROM skill_versions WHERE skill_id = ?").run(skillId);
|
|
910
|
+
this.db.prepare("DELETE FROM skill_embeddings WHERE skill_id = ?").run(skillId);
|
|
911
|
+
this.db.prepare("UPDATE chunks SET skill_id = NULL WHERE skill_id = ?").run(skillId);
|
|
912
|
+
const result = this.db.prepare("DELETE FROM skills WHERE id = ?").run(skillId);
|
|
913
|
+
return result.changes > 0;
|
|
914
|
+
}
|
|
915
|
+
|
|
857
916
|
// ─── Task CRUD ───
|
|
858
917
|
|
|
859
918
|
insertTask(task: Task): void {
|
|
@@ -989,6 +1048,24 @@ export class SqliteStore {
|
|
|
989
1048
|
return !!row;
|
|
990
1049
|
}
|
|
991
1050
|
|
|
1051
|
+
/**
|
|
1052
|
+
* Find an active chunk with the same content_hash within the same owner (agent dimension).
|
|
1053
|
+
* Returns the existing chunk ID if found, null otherwise.
|
|
1054
|
+
*/
|
|
1055
|
+
findActiveChunkByHash(content: string, owner?: string): string | null {
|
|
1056
|
+
const hash = contentHash(content);
|
|
1057
|
+
if (owner) {
|
|
1058
|
+
const row = this.db.prepare(
|
|
1059
|
+
"SELECT id FROM chunks WHERE content_hash = ? AND dedup_status = 'active' AND owner = ? LIMIT 1",
|
|
1060
|
+
).get(hash, owner) as { id: string } | undefined;
|
|
1061
|
+
return row?.id ?? null;
|
|
1062
|
+
}
|
|
1063
|
+
const row = this.db.prepare(
|
|
1064
|
+
"SELECT id FROM chunks WHERE content_hash = ? AND dedup_status = 'active' LIMIT 1",
|
|
1065
|
+
).get(hash) as { id: string } | undefined;
|
|
1066
|
+
return row?.id ?? null;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
992
1069
|
// ─── Util ───
|
|
993
1070
|
|
|
994
1071
|
getRecentChunkIds(limit: number): string[] {
|
|
@@ -1161,6 +1238,10 @@ export class SqliteStore {
|
|
|
1161
1238
|
// ─── Task-Skill Links ───
|
|
1162
1239
|
|
|
1163
1240
|
linkTaskSkill(taskId: string, skillId: string, relation: TaskSkillRelation, versionAt: number): void {
|
|
1241
|
+
const skillExists = this.db.prepare("SELECT 1 FROM skills WHERE id = ?").get(skillId);
|
|
1242
|
+
if (!skillExists) return;
|
|
1243
|
+
const taskExists = this.db.prepare("SELECT 1 FROM tasks WHERE id = ?").get(taskId);
|
|
1244
|
+
if (!taskExists) return;
|
|
1164
1245
|
this.db.prepare(`
|
|
1165
1246
|
INSERT OR REPLACE INTO task_skills (task_id, skill_id, relation, version_at, created_at)
|
|
1166
1247
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -1211,6 +1292,17 @@ export class SqliteStore {
|
|
|
1211
1292
|
.map(r => r.session_key);
|
|
1212
1293
|
}
|
|
1213
1294
|
|
|
1295
|
+
getSessionOwnerMap(sessionKeys: string[]): Map<string, string> {
|
|
1296
|
+
const result = new Map<string, string>();
|
|
1297
|
+
if (sessionKeys.length === 0) return result;
|
|
1298
|
+
const placeholders = sessionKeys.map(() => "?").join(",");
|
|
1299
|
+
const rows = this.db.prepare(
|
|
1300
|
+
`SELECT session_key, owner FROM chunks WHERE session_key IN (${placeholders}) AND owner IS NOT NULL GROUP BY session_key`,
|
|
1301
|
+
).all(...sessionKeys) as Array<{ session_key: string; owner: string }>;
|
|
1302
|
+
for (const r of rows) result.set(r.session_key, r.owner);
|
|
1303
|
+
return result;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1214
1306
|
close(): void {
|
|
1215
1307
|
this.db.close();
|
|
1216
1308
|
}
|
package/src/tools/memory-get.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { SqliteStore } from "../storage/sqlite";
|
|
|
2
2
|
import type { ToolDefinition, GetResult, ChunkRef } from "../types";
|
|
3
3
|
import { DEFAULTS } from "../types";
|
|
4
4
|
|
|
5
|
+
function resolveOwnerFilter(owner: unknown): string[] {
|
|
6
|
+
const resolvedOwner = typeof owner === "string" && owner.trim().length > 0 ? owner : "agent:main";
|
|
7
|
+
return resolvedOwner === "public" ? ["public"] : [resolvedOwner, "public"];
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export function createMemoryGetTool(store: SqliteStore): ToolDefinition {
|
|
6
11
|
return {
|
|
7
12
|
name: "memory_get",
|
|
@@ -36,7 +41,7 @@ export function createMemoryGetTool(store: SqliteStore): ToolDefinition {
|
|
|
36
41
|
DEFAULTS.getMaxCharsMax,
|
|
37
42
|
);
|
|
38
43
|
|
|
39
|
-
const chunk = store.getChunksByRef(ref);
|
|
44
|
+
const chunk = store.getChunksByRef(ref, resolveOwnerFilter(input.owner));
|
|
40
45
|
|
|
41
46
|
if (!chunk) {
|
|
42
47
|
return { error: `Chunk not found: ${ref.chunkId}` };
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { RecallEngine } from "../recall/engine";
|
|
2
2
|
import type { ToolDefinition } from "../types";
|
|
3
3
|
|
|
4
|
+
function resolveOwnerFilter(owner: unknown): string[] {
|
|
5
|
+
const resolvedOwner = typeof owner === "string" && owner.trim().length > 0 ? owner : "agent:main";
|
|
6
|
+
return resolvedOwner === "public" ? ["public"] : [resolvedOwner, "public"];
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
export function createMemorySearchTool(engine: RecallEngine): ToolDefinition {
|
|
5
10
|
return {
|
|
6
11
|
name: "memory_search",
|
|
@@ -29,6 +34,7 @@ export function createMemorySearchTool(engine: RecallEngine): ToolDefinition {
|
|
|
29
34
|
query: (input.query as string) ?? "",
|
|
30
35
|
maxResults: input.maxResults as number | undefined,
|
|
31
36
|
minScore: input.minScore as number | undefined,
|
|
37
|
+
ownerFilter: resolveOwnerFilter(input.owner),
|
|
32
38
|
});
|
|
33
39
|
return result;
|
|
34
40
|
},
|
|
@@ -2,6 +2,11 @@ import type { SqliteStore } from "../storage/sqlite";
|
|
|
2
2
|
import type { ToolDefinition, TimelineResult, TimelineEntry, ChunkRef } from "../types";
|
|
3
3
|
import { DEFAULTS } from "../types";
|
|
4
4
|
|
|
5
|
+
function resolveOwnerFilter(owner: unknown): string[] {
|
|
6
|
+
const resolvedOwner = typeof owner === "string" && owner.trim().length > 0 ? owner : "agent:main";
|
|
7
|
+
return resolvedOwner === "public" ? ["public"] : [resolvedOwner, "public"];
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export function createMemoryTimelineTool(store: SqliteStore): ToolDefinition {
|
|
6
11
|
return {
|
|
7
12
|
name: "memory_timeline",
|
|
@@ -33,18 +38,25 @@ export function createMemoryTimelineTool(store: SqliteStore): ToolDefinition {
|
|
|
33
38
|
const ref = input.ref as ChunkRef;
|
|
34
39
|
const window = (input.window as number) ?? DEFAULTS.timelineWindowDefault;
|
|
35
40
|
|
|
41
|
+
const ownerFilter = resolveOwnerFilter(input.owner);
|
|
42
|
+
const anchorChunk = store.getChunksByRef(ref, ownerFilter);
|
|
43
|
+
if (!anchorChunk) {
|
|
44
|
+
return { entries: [], anchorRef: ref } satisfies TimelineResult;
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
const neighbors = store.getNeighborChunks(
|
|
37
48
|
ref.sessionKey,
|
|
38
49
|
ref.turnId,
|
|
39
50
|
ref.seq,
|
|
40
51
|
window,
|
|
52
|
+
ownerFilter,
|
|
41
53
|
);
|
|
42
54
|
|
|
43
55
|
const entries: TimelineEntry[] = neighbors.map((chunk) => {
|
|
44
56
|
let relation: TimelineEntry["relation"] = "before";
|
|
45
57
|
if (chunk.id === ref.chunkId) {
|
|
46
58
|
relation = "current";
|
|
47
|
-
} else if (chunk.createdAt >
|
|
59
|
+
} else if (chunk.createdAt > anchorChunk.createdAt) {
|
|
48
60
|
relation = "after";
|
|
49
61
|
}
|
|
50
62
|
|
package/src/types.ts
CHANGED
|
@@ -247,6 +247,7 @@ export interface SkillEvolutionConfig {
|
|
|
247
247
|
minConfidence?: number;
|
|
248
248
|
maxSkillLines?: number;
|
|
249
249
|
autoInstall?: boolean;
|
|
250
|
+
summarizer?: SummarizerConfig;
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
export interface TelemetryConfig {
|
|
@@ -293,7 +294,7 @@ export const DEFAULTS = {
|
|
|
293
294
|
mmrLambda: 0.7,
|
|
294
295
|
recencyHalfLifeDays: 14,
|
|
295
296
|
vectorSearchMaxChunks: 0,
|
|
296
|
-
dedupSimilarityThreshold: 0.
|
|
297
|
+
dedupSimilarityThreshold: 0.60,
|
|
297
298
|
evidenceWrapperTag: "STORED_MEMORY",
|
|
298
299
|
excerptMinChars: 200,
|
|
299
300
|
excerptMaxChars: 500,
|