@mnemoai/core 1.1.0 → 1.1.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/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.d.ts.map +1 -0
- package/{index.ts → dist/index.js} +526 -1333
- package/dist/index.js.map +7 -0
- package/dist/src/access-tracker.d.ts +97 -0
- package/dist/src/access-tracker.d.ts.map +1 -0
- package/dist/src/access-tracker.js +184 -0
- package/dist/src/access-tracker.js.map +7 -0
- package/dist/src/adapters/chroma.d.ts +31 -0
- package/dist/src/adapters/chroma.d.ts.map +1 -0
- package/{src/adapters/chroma.ts → dist/src/adapters/chroma.js} +45 -107
- package/dist/src/adapters/chroma.js.map +7 -0
- package/dist/src/adapters/lancedb.d.ts +29 -0
- package/dist/src/adapters/lancedb.d.ts.map +1 -0
- package/{src/adapters/lancedb.ts → dist/src/adapters/lancedb.js} +41 -109
- package/dist/src/adapters/lancedb.js.map +7 -0
- package/dist/src/adapters/pgvector.d.ts +33 -0
- package/dist/src/adapters/pgvector.d.ts.map +1 -0
- package/{src/adapters/pgvector.ts → dist/src/adapters/pgvector.js} +42 -104
- package/dist/src/adapters/pgvector.js.map +7 -0
- package/dist/src/adapters/qdrant.d.ts +34 -0
- package/dist/src/adapters/qdrant.d.ts.map +1 -0
- package/dist/src/adapters/qdrant.js +132 -0
- package/dist/src/adapters/qdrant.js.map +7 -0
- package/dist/src/adaptive-retrieval.d.ts +14 -0
- package/dist/src/adaptive-retrieval.d.ts.map +1 -0
- package/dist/src/adaptive-retrieval.js +52 -0
- package/dist/src/adaptive-retrieval.js.map +7 -0
- package/dist/src/audit-log.d.ts +56 -0
- package/dist/src/audit-log.d.ts.map +1 -0
- package/dist/src/audit-log.js +139 -0
- package/dist/src/audit-log.js.map +7 -0
- package/dist/src/chunker.d.ts +45 -0
- package/dist/src/chunker.d.ts.map +1 -0
- package/dist/src/chunker.js +157 -0
- package/dist/src/chunker.js.map +7 -0
- package/dist/src/config.d.ts +70 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +142 -0
- package/dist/src/config.js.map +7 -0
- package/dist/src/decay-engine.d.ts +73 -0
- package/dist/src/decay-engine.d.ts.map +1 -0
- package/dist/src/decay-engine.js +119 -0
- package/dist/src/decay-engine.js.map +7 -0
- package/dist/src/embedder.d.ts +94 -0
- package/dist/src/embedder.d.ts.map +1 -0
- package/{src/embedder.ts → dist/src/embedder.js} +119 -317
- package/dist/src/embedder.js.map +7 -0
- package/dist/src/extraction-prompts.d.ts +12 -0
- package/dist/src/extraction-prompts.d.ts.map +1 -0
- package/dist/src/extraction-prompts.js +311 -0
- package/dist/src/extraction-prompts.js.map +7 -0
- package/dist/src/license.d.ts +29 -0
- package/dist/src/license.d.ts.map +1 -0
- package/{src/license.ts → dist/src/license.js} +42 -113
- package/dist/src/license.js.map +7 -0
- package/dist/src/llm-client.d.ts +23 -0
- package/dist/src/llm-client.d.ts.map +1 -0
- package/{src/llm-client.ts → dist/src/llm-client.js} +22 -55
- package/dist/src/llm-client.js.map +7 -0
- package/dist/src/logger.d.ts +33 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +35 -0
- package/dist/src/logger.js.map +7 -0
- package/dist/src/mcp-server.d.ts +16 -0
- package/dist/src/mcp-server.d.ts.map +1 -0
- package/{src/mcp-server.ts → dist/src/mcp-server.js} +81 -181
- package/dist/src/mcp-server.js.map +7 -0
- package/dist/src/memory-categories.d.ts +40 -0
- package/dist/src/memory-categories.d.ts.map +1 -0
- package/dist/src/memory-categories.js +33 -0
- package/dist/src/memory-categories.js.map +7 -0
- package/dist/src/memory-upgrader.d.ts +71 -0
- package/dist/src/memory-upgrader.d.ts.map +1 -0
- package/dist/src/memory-upgrader.js +238 -0
- package/dist/src/memory-upgrader.js.map +7 -0
- package/dist/src/migrate.d.ts +47 -0
- package/dist/src/migrate.d.ts.map +1 -0
- package/{src/migrate.ts → dist/src/migrate.js} +57 -165
- package/dist/src/migrate.js.map +7 -0
- package/dist/src/mnemo.d.ts +67 -0
- package/dist/src/mnemo.d.ts.map +1 -0
- package/dist/src/mnemo.js +66 -0
- package/dist/src/mnemo.js.map +7 -0
- package/dist/src/noise-filter.d.ts +23 -0
- package/dist/src/noise-filter.d.ts.map +1 -0
- package/dist/src/noise-filter.js +62 -0
- package/dist/src/noise-filter.js.map +7 -0
- package/dist/src/noise-prototypes.d.ts +40 -0
- package/dist/src/noise-prototypes.d.ts.map +1 -0
- package/dist/src/noise-prototypes.js +116 -0
- package/dist/src/noise-prototypes.js.map +7 -0
- package/dist/src/observability.d.ts +16 -0
- package/dist/src/observability.d.ts.map +1 -0
- package/dist/src/observability.js +53 -0
- package/dist/src/observability.js.map +7 -0
- package/dist/src/query-tracker.d.ts +27 -0
- package/dist/src/query-tracker.d.ts.map +1 -0
- package/dist/src/query-tracker.js +32 -0
- package/dist/src/query-tracker.js.map +7 -0
- package/dist/src/reflection-event-store.d.ts +44 -0
- package/dist/src/reflection-event-store.d.ts.map +1 -0
- package/dist/src/reflection-event-store.js +50 -0
- package/dist/src/reflection-event-store.js.map +7 -0
- package/dist/src/reflection-item-store.d.ts +58 -0
- package/dist/src/reflection-item-store.d.ts.map +1 -0
- package/dist/src/reflection-item-store.js +69 -0
- package/dist/src/reflection-item-store.js.map +7 -0
- package/dist/src/reflection-mapped-metadata.d.ts +47 -0
- package/dist/src/reflection-mapped-metadata.d.ts.map +1 -0
- package/dist/src/reflection-mapped-metadata.js +40 -0
- package/dist/src/reflection-mapped-metadata.js.map +7 -0
- package/dist/src/reflection-metadata.d.ts +11 -0
- package/dist/src/reflection-metadata.d.ts.map +1 -0
- package/dist/src/reflection-metadata.js +24 -0
- package/dist/src/reflection-metadata.js.map +7 -0
- package/dist/src/reflection-ranking.d.ts +13 -0
- package/dist/src/reflection-ranking.d.ts.map +1 -0
- package/{src/reflection-ranking.ts → dist/src/reflection-ranking.js} +12 -21
- package/dist/src/reflection-ranking.js.map +7 -0
- package/dist/src/reflection-retry.d.ts +30 -0
- package/dist/src/reflection-retry.d.ts.map +1 -0
- package/{src/reflection-retry.ts → dist/src/reflection-retry.js} +24 -64
- package/dist/src/reflection-retry.js.map +7 -0
- package/dist/src/reflection-slices.d.ts +42 -0
- package/dist/src/reflection-slices.d.ts.map +1 -0
- package/{src/reflection-slices.ts → dist/src/reflection-slices.js} +60 -136
- package/dist/src/reflection-slices.js.map +7 -0
- package/dist/src/reflection-store.d.ts +85 -0
- package/dist/src/reflection-store.d.ts.map +1 -0
- package/dist/src/reflection-store.js +407 -0
- package/dist/src/reflection-store.js.map +7 -0
- package/dist/src/resonance-state.d.ts +19 -0
- package/dist/src/resonance-state.d.ts.map +1 -0
- package/{src/resonance-state.ts → dist/src/resonance-state.js} +13 -42
- package/dist/src/resonance-state.js.map +7 -0
- package/dist/src/retriever.d.ts +228 -0
- package/dist/src/retriever.d.ts.map +1 -0
- package/dist/src/retriever.js +1006 -0
- package/dist/src/retriever.js.map +7 -0
- package/dist/src/scopes.d.ts +58 -0
- package/dist/src/scopes.d.ts.map +1 -0
- package/dist/src/scopes.js +252 -0
- package/dist/src/scopes.js.map +7 -0
- package/dist/src/self-improvement-files.d.ts +20 -0
- package/dist/src/self-improvement-files.d.ts.map +1 -0
- package/{src/self-improvement-files.ts → dist/src/self-improvement-files.js} +24 -49
- package/dist/src/self-improvement-files.js.map +7 -0
- package/dist/src/semantic-gate.d.ts +24 -0
- package/dist/src/semantic-gate.d.ts.map +1 -0
- package/dist/src/semantic-gate.js +86 -0
- package/dist/src/semantic-gate.js.map +7 -0
- package/dist/src/session-recovery.d.ts +9 -0
- package/dist/src/session-recovery.d.ts.map +1 -0
- package/{src/session-recovery.ts → dist/src/session-recovery.js} +40 -57
- package/dist/src/session-recovery.js.map +7 -0
- package/dist/src/smart-extractor.d.ts +107 -0
- package/dist/src/smart-extractor.d.ts.map +1 -0
- package/{src/smart-extractor.ts → dist/src/smart-extractor.js} +130 -383
- package/dist/src/smart-extractor.js.map +7 -0
- package/dist/src/smart-metadata.d.ts +103 -0
- package/dist/src/smart-metadata.d.ts.map +1 -0
- package/dist/src/smart-metadata.js +361 -0
- package/dist/src/smart-metadata.js.map +7 -0
- package/dist/src/storage-adapter.d.ts +102 -0
- package/dist/src/storage-adapter.d.ts.map +1 -0
- package/dist/src/storage-adapter.js +22 -0
- package/dist/src/storage-adapter.js.map +7 -0
- package/dist/src/store.d.ts +108 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +939 -0
- package/dist/src/store.js.map +7 -0
- package/dist/src/tier-manager.d.ts +57 -0
- package/dist/src/tier-manager.d.ts.map +1 -0
- package/dist/src/tier-manager.js +80 -0
- package/dist/src/tier-manager.js.map +7 -0
- package/dist/src/tools.d.ts +43 -0
- package/dist/src/tools.d.ts.map +1 -0
- package/dist/src/tools.js +1075 -0
- package/dist/src/tools.js.map +7 -0
- package/dist/src/wal-recovery.d.ts +30 -0
- package/dist/src/wal-recovery.d.ts.map +1 -0
- package/{src/wal-recovery.ts → dist/src/wal-recovery.js} +26 -79
- package/dist/src/wal-recovery.js.map +7 -0
- package/package.json +21 -2
- package/openclaw.plugin.json +0 -815
- package/src/access-tracker.ts +0 -341
- package/src/adapters/README.md +0 -78
- package/src/adapters/qdrant.ts +0 -191
- package/src/adaptive-retrieval.ts +0 -90
- package/src/audit-log.ts +0 -238
- package/src/chunker.ts +0 -254
- package/src/config.ts +0 -271
- package/src/decay-engine.ts +0 -238
- package/src/extraction-prompts.ts +0 -339
- package/src/memory-categories.ts +0 -71
- package/src/memory-upgrader.ts +0 -388
- package/src/mnemo.ts +0 -142
- package/src/noise-filter.ts +0 -97
- package/src/noise-prototypes.ts +0 -164
- package/src/observability.ts +0 -81
- package/src/query-tracker.ts +0 -57
- package/src/reflection-event-store.ts +0 -98
- package/src/reflection-item-store.ts +0 -112
- package/src/reflection-mapped-metadata.ts +0 -84
- package/src/reflection-metadata.ts +0 -23
- package/src/reflection-store.ts +0 -602
- package/src/retriever.ts +0 -1510
- package/src/scopes.ts +0 -375
- package/src/semantic-gate.ts +0 -121
- package/src/smart-metadata.ts +0 -561
- package/src/storage-adapter.ts +0 -153
- package/src/store.ts +0 -1330
- package/src/tier-manager.ts +0 -189
- package/src/tools.ts +0 -1292
- package/test/core.test.mjs +0 -301
|
@@ -0,0 +1,1075 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { isNoise } from "./noise-filter.js";
|
|
6
|
+
import {
|
|
7
|
+
buildSmartMetadata,
|
|
8
|
+
parseSmartMetadata,
|
|
9
|
+
stringifySmartMetadata
|
|
10
|
+
} from "./smart-metadata.js";
|
|
11
|
+
import { appendSelfImprovementEntry, ensureSelfImprovementLearningFiles } from "./self-improvement-files.js";
|
|
12
|
+
import { getDisplayCategoryTag } from "./reflection-metadata.js";
|
|
13
|
+
import { log } from "./logger.js";
|
|
14
|
+
const MEMORY_CATEGORIES = [
|
|
15
|
+
"preference",
|
|
16
|
+
"fact",
|
|
17
|
+
"decision",
|
|
18
|
+
"entity",
|
|
19
|
+
"reflection",
|
|
20
|
+
"other"
|
|
21
|
+
];
|
|
22
|
+
function stringEnum(values) {
|
|
23
|
+
return Type.Unsafe({
|
|
24
|
+
type: "string",
|
|
25
|
+
enum: [...values]
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function resolveAgentId(runtimeAgentId, fallback) {
|
|
29
|
+
if (typeof runtimeAgentId === "string" && runtimeAgentId.trim().length > 0) return runtimeAgentId;
|
|
30
|
+
if (typeof fallback === "string" && fallback.trim().length > 0) return fallback;
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
function clampInt(value, min, max) {
|
|
34
|
+
if (!Number.isFinite(value)) return min;
|
|
35
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
36
|
+
}
|
|
37
|
+
function clamp01(value, fallback = 0.7) {
|
|
38
|
+
if (!Number.isFinite(value)) return fallback;
|
|
39
|
+
return Math.min(1, Math.max(0, value));
|
|
40
|
+
}
|
|
41
|
+
function sanitizeMemoryForSerialization(results) {
|
|
42
|
+
return results.map((r) => ({
|
|
43
|
+
id: r.entry.id,
|
|
44
|
+
text: r.entry.text,
|
|
45
|
+
category: getDisplayCategoryTag(r.entry),
|
|
46
|
+
rawCategory: r.entry.category,
|
|
47
|
+
scope: r.entry.scope,
|
|
48
|
+
importance: r.entry.importance,
|
|
49
|
+
score: r.score,
|
|
50
|
+
sources: r.sources
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
function parseAgentIdFromSessionKey(sessionKey) {
|
|
54
|
+
if (!sessionKey) return void 0;
|
|
55
|
+
const m = /^agent:([^:]+):/.exec(sessionKey);
|
|
56
|
+
return m?.[1];
|
|
57
|
+
}
|
|
58
|
+
function resolveRuntimeAgentId(staticAgentId, runtimeCtx) {
|
|
59
|
+
if (!runtimeCtx || typeof runtimeCtx !== "object") return staticAgentId;
|
|
60
|
+
const ctx = runtimeCtx;
|
|
61
|
+
const ctxAgentId = typeof ctx.agentId === "string" ? ctx.agentId : void 0;
|
|
62
|
+
const ctxSessionKey = typeof ctx.sessionKey === "string" ? ctx.sessionKey : void 0;
|
|
63
|
+
return ctxAgentId || parseAgentIdFromSessionKey(ctxSessionKey) || staticAgentId;
|
|
64
|
+
}
|
|
65
|
+
function resolveToolContext(base, runtimeCtx) {
|
|
66
|
+
return {
|
|
67
|
+
...base,
|
|
68
|
+
agentId: resolveRuntimeAgentId(base.agentId, runtimeCtx)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function sleep(ms) {
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
73
|
+
}
|
|
74
|
+
async function retrieveWithRetry(retriever, params) {
|
|
75
|
+
let results = await retriever.retrieve(params);
|
|
76
|
+
if (results.length === 0) {
|
|
77
|
+
await sleep(75);
|
|
78
|
+
results = await retriever.retrieve(params);
|
|
79
|
+
}
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
function resolveWorkspaceDir(toolCtx, fallback) {
|
|
83
|
+
const runtime = toolCtx;
|
|
84
|
+
const runtimePath = typeof runtime?.workspaceDir === "string" ? runtime.workspaceDir.trim() : "";
|
|
85
|
+
if (runtimePath) return runtimePath;
|
|
86
|
+
if (fallback && fallback.trim()) return fallback;
|
|
87
|
+
return join(homedir(), ".openclaw", "workspace");
|
|
88
|
+
}
|
|
89
|
+
function escapeRegExp(input) {
|
|
90
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
91
|
+
}
|
|
92
|
+
function registerSelfImprovementLogTool(api, context) {
|
|
93
|
+
api.registerTool(
|
|
94
|
+
(toolCtx) => ({
|
|
95
|
+
name: "self_improvement_log",
|
|
96
|
+
label: "Self-Improvement Log",
|
|
97
|
+
description: "Log structured learning/error entries into .learnings for governance and later distillation.",
|
|
98
|
+
parameters: Type.Object({
|
|
99
|
+
type: stringEnum(["learning", "error"]),
|
|
100
|
+
summary: Type.String({ description: "One-line summary" }),
|
|
101
|
+
details: Type.Optional(Type.String({ description: "Detailed context or error output" })),
|
|
102
|
+
suggestedAction: Type.Optional(Type.String({ description: "Concrete action to prevent recurrence" })),
|
|
103
|
+
category: Type.Optional(Type.String({ description: "learning category (correction/best_practice/knowledge_gap) when type=learning" })),
|
|
104
|
+
area: Type.Optional(Type.String({ description: "frontend|backend|infra|tests|docs|config or custom area" })),
|
|
105
|
+
priority: Type.Optional(Type.String({ description: "low|medium|high|critical" }))
|
|
106
|
+
}),
|
|
107
|
+
async execute(_toolCallId, params) {
|
|
108
|
+
const {
|
|
109
|
+
type,
|
|
110
|
+
summary,
|
|
111
|
+
details = "",
|
|
112
|
+
suggestedAction = "",
|
|
113
|
+
category = "best_practice",
|
|
114
|
+
area = "config",
|
|
115
|
+
priority = "medium"
|
|
116
|
+
} = params;
|
|
117
|
+
try {
|
|
118
|
+
const workspaceDir = resolveWorkspaceDir(toolCtx, context.workspaceDir);
|
|
119
|
+
const { id: entryId, filePath } = await appendSelfImprovementEntry({
|
|
120
|
+
baseDir: workspaceDir,
|
|
121
|
+
type,
|
|
122
|
+
summary,
|
|
123
|
+
details,
|
|
124
|
+
suggestedAction,
|
|
125
|
+
category,
|
|
126
|
+
area,
|
|
127
|
+
priority,
|
|
128
|
+
source: "mnemo/self_improvement_log"
|
|
129
|
+
});
|
|
130
|
+
const fileName = type === "learning" ? "LEARNINGS.md" : "ERRORS.md";
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: `Logged ${type} entry ${entryId} to .learnings/${fileName}` }],
|
|
133
|
+
details: { action: "logged", type, id: entryId, filePath }
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: `Failed to log self-improvement entry: ${error instanceof Error ? error.message : String(error)}` }],
|
|
138
|
+
details: { error: "self_improvement_log_failed", message: String(error) }
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}),
|
|
143
|
+
{ name: "self_improvement_log" }
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
function registerSelfImprovementExtractSkillTool(api, context) {
|
|
147
|
+
api.registerTool(
|
|
148
|
+
(toolCtx) => ({
|
|
149
|
+
name: "self_improvement_extract_skill",
|
|
150
|
+
label: "Extract Skill From Learning",
|
|
151
|
+
description: "Create a new skill scaffold from a learning entry and mark the source learning as promoted_to_skill.",
|
|
152
|
+
parameters: Type.Object({
|
|
153
|
+
learningId: Type.String({ description: "Learning ID like LRN-YYYYMMDD-001" }),
|
|
154
|
+
skillName: Type.String({ description: "Skill folder name, lowercase with hyphens" }),
|
|
155
|
+
sourceFile: Type.Optional(stringEnum(["LEARNINGS.md", "ERRORS.md"])),
|
|
156
|
+
outputDir: Type.Optional(Type.String({ description: "Relative output dir under workspace (default: skills)" }))
|
|
157
|
+
}),
|
|
158
|
+
async execute(_toolCallId, params) {
|
|
159
|
+
const { learningId, skillName, sourceFile = "LEARNINGS.md", outputDir = "skills" } = params;
|
|
160
|
+
try {
|
|
161
|
+
if (!/^(LRN|ERR)-\d{8}-\d{3}$/.test(learningId)) {
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: "text", text: "Invalid learningId format. Use LRN-YYYYMMDD-001 / ERR-..." }],
|
|
164
|
+
details: { error: "invalid_learning_id" }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(skillName)) {
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: "text", text: "Invalid skillName. Use lowercase letters, numbers, and hyphens only." }],
|
|
170
|
+
details: { error: "invalid_skill_name" }
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const workspaceDir = resolveWorkspaceDir(toolCtx, context.workspaceDir);
|
|
174
|
+
await ensureSelfImprovementLearningFiles(workspaceDir);
|
|
175
|
+
const learningsPath = join(workspaceDir, ".learnings", sourceFile);
|
|
176
|
+
const learningBody = await readFile(learningsPath, "utf-8");
|
|
177
|
+
const escapedLearningId = escapeRegExp(learningId.trim());
|
|
178
|
+
const entryRegex = new RegExp(`## \\[${escapedLearningId}\\][\\s\\S]*?(?=\\n## \\[|$)`, "m");
|
|
179
|
+
const match = learningBody.match(entryRegex);
|
|
180
|
+
if (!match) {
|
|
181
|
+
return {
|
|
182
|
+
content: [{ type: "text", text: `Learning entry ${learningId} not found in .learnings/${sourceFile}` }],
|
|
183
|
+
details: { error: "learning_not_found", learningId, sourceFile }
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const summaryMatch = match[0].match(/### Summary\n([\s\S]*?)\n###/m);
|
|
187
|
+
const summary = (summaryMatch?.[1] ?? "Summarize the source learning here.").trim();
|
|
188
|
+
const safeOutputDir = outputDir.replace(/\\/g, "/").split("/").filter((segment) => segment && segment !== "." && segment !== "..").join("/");
|
|
189
|
+
const skillDir = join(workspaceDir, safeOutputDir || "skills", skillName);
|
|
190
|
+
await mkdir(skillDir, { recursive: true });
|
|
191
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
192
|
+
const skillTitle = skillName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
|
|
193
|
+
const skillContent = [
|
|
194
|
+
"---",
|
|
195
|
+
`name: ${skillName}`,
|
|
196
|
+
`description: "Extracted from learning ${learningId}. Replace with a concise description."`,
|
|
197
|
+
"---",
|
|
198
|
+
"",
|
|
199
|
+
`# ${skillTitle}`,
|
|
200
|
+
"",
|
|
201
|
+
"## Why",
|
|
202
|
+
summary,
|
|
203
|
+
"",
|
|
204
|
+
"## When To Use",
|
|
205
|
+
"- [TODO] Define trigger conditions",
|
|
206
|
+
"",
|
|
207
|
+
"## Steps",
|
|
208
|
+
"1. [TODO] Add repeatable workflow steps",
|
|
209
|
+
"2. [TODO] Add verification steps",
|
|
210
|
+
"",
|
|
211
|
+
"## Source Learning",
|
|
212
|
+
`- Learning ID: ${learningId}`,
|
|
213
|
+
`- Source File: .learnings/${sourceFile}`,
|
|
214
|
+
""
|
|
215
|
+
].join("\n");
|
|
216
|
+
await writeFile(skillPath, skillContent, "utf-8");
|
|
217
|
+
const promotedMarker = `**Status**: promoted_to_skill`;
|
|
218
|
+
const skillPathMarker = `- Skill-Path: ${safeOutputDir || "skills"}/${skillName}`;
|
|
219
|
+
let updatedEntry = match[0];
|
|
220
|
+
updatedEntry = updatedEntry.includes("**Status**:") ? updatedEntry.replace(/\*\*Status\*\*:\s*.+/m, promotedMarker) : `${updatedEntry.trimEnd()}
|
|
221
|
+
${promotedMarker}
|
|
222
|
+
`;
|
|
223
|
+
if (!updatedEntry.includes("Skill-Path:")) {
|
|
224
|
+
updatedEntry = `${updatedEntry.trimEnd()}
|
|
225
|
+
${skillPathMarker}
|
|
226
|
+
`;
|
|
227
|
+
}
|
|
228
|
+
const updatedLearningBody = learningBody.replace(match[0], updatedEntry);
|
|
229
|
+
await writeFile(learningsPath, updatedLearningBody, "utf-8");
|
|
230
|
+
return {
|
|
231
|
+
content: [{ type: "text", text: `Extracted skill scaffold to ${safeOutputDir || "skills"}/${skillName}/SKILL.md and updated ${learningId}.` }],
|
|
232
|
+
details: {
|
|
233
|
+
action: "skill_extracted",
|
|
234
|
+
learningId,
|
|
235
|
+
sourceFile,
|
|
236
|
+
skillPath: `${safeOutputDir || "skills"}/${skillName}/SKILL.md`
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return {
|
|
241
|
+
content: [{ type: "text", text: `Failed to extract skill: ${error instanceof Error ? error.message : String(error)}` }],
|
|
242
|
+
details: { error: "self_improvement_extract_skill_failed", message: String(error) }
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}),
|
|
247
|
+
{ name: "self_improvement_extract_skill" }
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
function registerSelfImprovementReviewTool(api, context) {
|
|
251
|
+
api.registerTool(
|
|
252
|
+
(toolCtx) => ({
|
|
253
|
+
name: "self_improvement_review",
|
|
254
|
+
label: "Self-Improvement Review",
|
|
255
|
+
description: "Summarize governance backlog from .learnings files (pending/high-priority/promoted counts).",
|
|
256
|
+
parameters: Type.Object({}),
|
|
257
|
+
async execute() {
|
|
258
|
+
try {
|
|
259
|
+
const workspaceDir = resolveWorkspaceDir(toolCtx, context.workspaceDir);
|
|
260
|
+
await ensureSelfImprovementLearningFiles(workspaceDir);
|
|
261
|
+
const learningsDir = join(workspaceDir, ".learnings");
|
|
262
|
+
const files = ["LEARNINGS.md", "ERRORS.md"];
|
|
263
|
+
const stats = { pending: 0, high: 0, promoted: 0, total: 0 };
|
|
264
|
+
for (const f of files) {
|
|
265
|
+
const content = await readFile(join(learningsDir, f), "utf-8").catch(() => "");
|
|
266
|
+
stats.total += (content.match(/^## \[/gm) || []).length;
|
|
267
|
+
stats.pending += (content.match(/\*\*Status\*\*:\s*pending/gi) || []).length;
|
|
268
|
+
stats.high += (content.match(/\*\*Priority\*\*:\s*(high|critical)/gi) || []).length;
|
|
269
|
+
stats.promoted += (content.match(/\*\*Status\*\*:\s*promoted(_to_skill)?/gi) || []).length;
|
|
270
|
+
}
|
|
271
|
+
const text = [
|
|
272
|
+
"Self-Improvement Governance Snapshot:",
|
|
273
|
+
`- Total entries: ${stats.total}`,
|
|
274
|
+
`- Pending: ${stats.pending}`,
|
|
275
|
+
`- High/Critical: ${stats.high}`,
|
|
276
|
+
`- Promoted: ${stats.promoted}`,
|
|
277
|
+
"",
|
|
278
|
+
"Recommended loop:",
|
|
279
|
+
"1) Resolve high-priority pending entries",
|
|
280
|
+
"2) Distill reusable rules into AGENTS.md / SOUL.md / TOOLS.md",
|
|
281
|
+
"3) Extract repeatable patterns as skills"
|
|
282
|
+
].join("\n");
|
|
283
|
+
return {
|
|
284
|
+
content: [{ type: "text", text }],
|
|
285
|
+
details: { action: "review", stats }
|
|
286
|
+
};
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return {
|
|
289
|
+
content: [{ type: "text", text: `Failed to review self-improvement backlog: ${error instanceof Error ? error.message : String(error)}` }],
|
|
290
|
+
details: { error: "self_improvement_review_failed", message: String(error) }
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}),
|
|
295
|
+
{ name: "self_improvement_review" }
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
function registerMemoryRecallTool(api, context) {
|
|
299
|
+
api.registerTool(
|
|
300
|
+
(toolCtx) => {
|
|
301
|
+
const runtimeContext = resolveToolContext(context, toolCtx);
|
|
302
|
+
return {
|
|
303
|
+
name: "memory_recall",
|
|
304
|
+
label: "Memory Recall",
|
|
305
|
+
description: "Search through long-term memories using hybrid retrieval (vector + keyword search). Use when you need context about user preferences, past decisions, or previously discussed topics.",
|
|
306
|
+
parameters: Type.Object({
|
|
307
|
+
query: Type.String({
|
|
308
|
+
description: "Search query for finding relevant memories"
|
|
309
|
+
}),
|
|
310
|
+
limit: Type.Optional(
|
|
311
|
+
Type.Number({
|
|
312
|
+
description: "Max results to return (default: 5, max: 20)"
|
|
313
|
+
})
|
|
314
|
+
),
|
|
315
|
+
scope: Type.Optional(
|
|
316
|
+
Type.String({
|
|
317
|
+
description: "Specific memory scope to search in (optional)"
|
|
318
|
+
})
|
|
319
|
+
),
|
|
320
|
+
category: Type.Optional(stringEnum(MEMORY_CATEGORIES))
|
|
321
|
+
}),
|
|
322
|
+
async execute(_toolCallId, params) {
|
|
323
|
+
const {
|
|
324
|
+
query,
|
|
325
|
+
limit = 5,
|
|
326
|
+
scope,
|
|
327
|
+
category
|
|
328
|
+
} = params;
|
|
329
|
+
try {
|
|
330
|
+
const safeLimit = clampInt(limit, 1, 20);
|
|
331
|
+
const agentId = runtimeContext.agentId;
|
|
332
|
+
let scopeFilter = runtimeContext.scopeManager.getAccessibleScopes(agentId);
|
|
333
|
+
if (scope) {
|
|
334
|
+
if (runtimeContext.scopeManager.isAccessible(scope, agentId)) {
|
|
335
|
+
scopeFilter = [scope];
|
|
336
|
+
} else {
|
|
337
|
+
return {
|
|
338
|
+
content: [
|
|
339
|
+
{ type: "text", text: `Access denied to scope: ${scope}` }
|
|
340
|
+
],
|
|
341
|
+
details: {
|
|
342
|
+
error: "scope_access_denied",
|
|
343
|
+
requestedScope: scope
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const results = await retrieveWithRetry(runtimeContext.retriever, {
|
|
349
|
+
query,
|
|
350
|
+
limit: safeLimit,
|
|
351
|
+
scopeFilter,
|
|
352
|
+
category,
|
|
353
|
+
source: "manual"
|
|
354
|
+
});
|
|
355
|
+
if (results.length === 0) {
|
|
356
|
+
return {
|
|
357
|
+
content: [{ type: "text", text: "No relevant memories found." }],
|
|
358
|
+
details: { count: 0, query, scopes: scopeFilter }
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
await Promise.allSettled(
|
|
363
|
+
results.map((result) => {
|
|
364
|
+
const meta = parseSmartMetadata(result.entry.metadata, result.entry);
|
|
365
|
+
return runtimeContext.store.patchMetadata(
|
|
366
|
+
result.entry.id,
|
|
367
|
+
{
|
|
368
|
+
access_count: meta.access_count + 1,
|
|
369
|
+
last_accessed_at: now
|
|
370
|
+
},
|
|
371
|
+
scopeFilter
|
|
372
|
+
);
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
const text = results.map((r, i) => {
|
|
376
|
+
const categoryTag = getDisplayCategoryTag(r.entry);
|
|
377
|
+
return `${i + 1}. [${r.entry.id}] [${categoryTag}] ${r.entry.text}`;
|
|
378
|
+
}).join("\n");
|
|
379
|
+
return {
|
|
380
|
+
content: [
|
|
381
|
+
{
|
|
382
|
+
type: "text",
|
|
383
|
+
text: `Found ${results.length} memories:
|
|
384
|
+
|
|
385
|
+
${text}`
|
|
386
|
+
}
|
|
387
|
+
],
|
|
388
|
+
details: {
|
|
389
|
+
count: results.length,
|
|
390
|
+
memories: sanitizeMemoryForSerialization(results),
|
|
391
|
+
query,
|
|
392
|
+
scopes: scopeFilter,
|
|
393
|
+
retrievalMode: runtimeContext.retriever.getConfig().mode
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
} catch (error) {
|
|
397
|
+
return {
|
|
398
|
+
content: [
|
|
399
|
+
{
|
|
400
|
+
type: "text",
|
|
401
|
+
text: `Memory recall failed: ${error instanceof Error ? error.message : String(error)}`
|
|
402
|
+
}
|
|
403
|
+
],
|
|
404
|
+
details: { error: "recall_failed", message: String(error) }
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
{ name: "memory_recall" }
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
function registerMemoryStoreTool(api, context) {
|
|
414
|
+
api.registerTool(
|
|
415
|
+
(toolCtx) => {
|
|
416
|
+
const runtimeContext = resolveToolContext(context, toolCtx);
|
|
417
|
+
return {
|
|
418
|
+
name: "memory_store",
|
|
419
|
+
label: "Memory Store",
|
|
420
|
+
description: "Save important information in long-term memory. Use for preferences, facts, decisions, and other notable information.",
|
|
421
|
+
parameters: Type.Object({
|
|
422
|
+
text: Type.String({ description: "Information to remember" }),
|
|
423
|
+
importance: Type.Optional(
|
|
424
|
+
Type.Number({ description: "Importance score 0-1 (default: 0.7)" })
|
|
425
|
+
),
|
|
426
|
+
category: Type.Optional(stringEnum(MEMORY_CATEGORIES)),
|
|
427
|
+
scope: Type.Optional(
|
|
428
|
+
Type.String({
|
|
429
|
+
description: "Memory scope (optional, defaults to agent scope)"
|
|
430
|
+
})
|
|
431
|
+
)
|
|
432
|
+
}),
|
|
433
|
+
async execute(_toolCallId, params) {
|
|
434
|
+
const {
|
|
435
|
+
text,
|
|
436
|
+
importance = 0.7,
|
|
437
|
+
category = "other",
|
|
438
|
+
scope
|
|
439
|
+
} = params;
|
|
440
|
+
try {
|
|
441
|
+
const agentId = runtimeContext.agentId;
|
|
442
|
+
let targetScope = scope || runtimeContext.scopeManager.getDefaultScope(agentId);
|
|
443
|
+
if (!runtimeContext.scopeManager.isAccessible(targetScope, agentId)) {
|
|
444
|
+
return {
|
|
445
|
+
content: [
|
|
446
|
+
{
|
|
447
|
+
type: "text",
|
|
448
|
+
text: `Access denied to scope: ${targetScope}`
|
|
449
|
+
}
|
|
450
|
+
],
|
|
451
|
+
details: {
|
|
452
|
+
error: "scope_access_denied",
|
|
453
|
+
requestedScope: targetScope
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (isNoise(text)) {
|
|
458
|
+
return {
|
|
459
|
+
content: [
|
|
460
|
+
{
|
|
461
|
+
type: "text",
|
|
462
|
+
text: `Skipped: text detected as noise (greeting, boilerplate, or meta-question)`
|
|
463
|
+
}
|
|
464
|
+
],
|
|
465
|
+
details: { action: "noise_filtered", text: text.slice(0, 60) }
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const safeImportance = clamp01(importance, 0.7);
|
|
469
|
+
const vector = await runtimeContext.embedder.embedPassage(text);
|
|
470
|
+
let existing = [];
|
|
471
|
+
try {
|
|
472
|
+
existing = await runtimeContext.store.vectorSearch(vector, 1, 0.1, [
|
|
473
|
+
targetScope
|
|
474
|
+
]);
|
|
475
|
+
} catch (err) {
|
|
476
|
+
log.warn(
|
|
477
|
+
`duplicate pre-check failed, continue store: ${String(err)}`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (existing.length > 0 && existing[0].score > 0.98) {
|
|
481
|
+
return {
|
|
482
|
+
content: [
|
|
483
|
+
{
|
|
484
|
+
type: "text",
|
|
485
|
+
text: `Similar memory already exists: "${existing[0].entry.text}"`
|
|
486
|
+
}
|
|
487
|
+
],
|
|
488
|
+
details: {
|
|
489
|
+
action: "duplicate",
|
|
490
|
+
existingId: existing[0].entry.id,
|
|
491
|
+
existingText: existing[0].entry.text,
|
|
492
|
+
existingScope: existing[0].entry.scope,
|
|
493
|
+
similarity: existing[0].score
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
const entry = await runtimeContext.store.store({
|
|
498
|
+
text,
|
|
499
|
+
vector,
|
|
500
|
+
importance: safeImportance,
|
|
501
|
+
category,
|
|
502
|
+
scope: targetScope,
|
|
503
|
+
metadata: stringifySmartMetadata(
|
|
504
|
+
buildSmartMetadata(
|
|
505
|
+
{
|
|
506
|
+
text,
|
|
507
|
+
category,
|
|
508
|
+
importance: safeImportance
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
l0_abstract: text,
|
|
512
|
+
l1_overview: `- ${text}`,
|
|
513
|
+
l2_content: text
|
|
514
|
+
}
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
});
|
|
518
|
+
if (context.mdMirror) {
|
|
519
|
+
await context.mdMirror(
|
|
520
|
+
{ text, category, scope: targetScope, timestamp: entry.timestamp },
|
|
521
|
+
{ source: "memory_store", agentId }
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
content: [
|
|
526
|
+
{
|
|
527
|
+
type: "text",
|
|
528
|
+
text: `Stored: "${text.slice(0, 100)}${text.length > 100 ? "..." : ""}" in scope '${targetScope}'`
|
|
529
|
+
}
|
|
530
|
+
],
|
|
531
|
+
details: {
|
|
532
|
+
action: "created",
|
|
533
|
+
id: entry.id,
|
|
534
|
+
scope: entry.scope,
|
|
535
|
+
category: entry.category,
|
|
536
|
+
importance: entry.importance
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
} catch (error) {
|
|
540
|
+
return {
|
|
541
|
+
content: [
|
|
542
|
+
{
|
|
543
|
+
type: "text",
|
|
544
|
+
text: `Memory storage failed: ${error instanceof Error ? error.message : String(error)}`
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
details: { error: "store_failed", message: String(error) }
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
},
|
|
553
|
+
{ name: "memory_store" }
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
function registerMemoryForgetTool(api, context) {
|
|
557
|
+
api.registerTool(
|
|
558
|
+
(toolCtx) => {
|
|
559
|
+
const agentId = resolveAgentId(toolCtx?.agentId, context.agentId) ?? "main";
|
|
560
|
+
return {
|
|
561
|
+
name: "memory_forget",
|
|
562
|
+
label: "Memory Forget",
|
|
563
|
+
description: "Delete specific memories. Supports both search-based and direct ID-based deletion.",
|
|
564
|
+
parameters: Type.Object({
|
|
565
|
+
query: Type.Optional(
|
|
566
|
+
Type.String({ description: "Search query to find memory to delete" })
|
|
567
|
+
),
|
|
568
|
+
memoryId: Type.Optional(
|
|
569
|
+
Type.String({ description: "Specific memory ID to delete" })
|
|
570
|
+
),
|
|
571
|
+
scope: Type.Optional(
|
|
572
|
+
Type.String({
|
|
573
|
+
description: "Scope to search/delete from (optional)"
|
|
574
|
+
})
|
|
575
|
+
)
|
|
576
|
+
}),
|
|
577
|
+
async execute(_toolCallId, params, _signal, _onUpdate, runtimeCtx) {
|
|
578
|
+
const { query, memoryId, scope } = params;
|
|
579
|
+
try {
|
|
580
|
+
const agentId2 = resolveRuntimeAgentId(context.agentId, runtimeCtx);
|
|
581
|
+
let scopeFilter = context.scopeManager.getAccessibleScopes(agentId2);
|
|
582
|
+
if (scope) {
|
|
583
|
+
if (context.scopeManager.isAccessible(scope, agentId2)) {
|
|
584
|
+
scopeFilter = [scope];
|
|
585
|
+
} else {
|
|
586
|
+
return {
|
|
587
|
+
content: [
|
|
588
|
+
{ type: "text", text: `Access denied to scope: ${scope}` }
|
|
589
|
+
],
|
|
590
|
+
details: {
|
|
591
|
+
error: "scope_access_denied",
|
|
592
|
+
requestedScope: scope
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (memoryId) {
|
|
598
|
+
const deleted = await context.store.delete(memoryId, scopeFilter);
|
|
599
|
+
if (deleted) {
|
|
600
|
+
return {
|
|
601
|
+
content: [
|
|
602
|
+
{ type: "text", text: `Memory ${memoryId} forgotten.` }
|
|
603
|
+
],
|
|
604
|
+
details: { action: "deleted", id: memoryId }
|
|
605
|
+
};
|
|
606
|
+
} else {
|
|
607
|
+
return {
|
|
608
|
+
content: [
|
|
609
|
+
{
|
|
610
|
+
type: "text",
|
|
611
|
+
text: `Memory ${memoryId} not found or access denied.`
|
|
612
|
+
}
|
|
613
|
+
],
|
|
614
|
+
details: { error: "not_found", id: memoryId }
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (query) {
|
|
619
|
+
const results = await retrieveWithRetry(context.retriever, {
|
|
620
|
+
query,
|
|
621
|
+
limit: 5,
|
|
622
|
+
scopeFilter
|
|
623
|
+
});
|
|
624
|
+
if (results.length === 0) {
|
|
625
|
+
return {
|
|
626
|
+
content: [
|
|
627
|
+
{ type: "text", text: "No matching memories found." }
|
|
628
|
+
],
|
|
629
|
+
details: { found: 0, query }
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
if (results.length === 1 && results[0].score > 0.9) {
|
|
633
|
+
const deleted = await context.store.delete(
|
|
634
|
+
results[0].entry.id,
|
|
635
|
+
scopeFilter
|
|
636
|
+
);
|
|
637
|
+
if (deleted) {
|
|
638
|
+
return {
|
|
639
|
+
content: [
|
|
640
|
+
{
|
|
641
|
+
type: "text",
|
|
642
|
+
text: `Forgotten: "${results[0].entry.text}"`
|
|
643
|
+
}
|
|
644
|
+
],
|
|
645
|
+
details: { action: "deleted", id: results[0].entry.id }
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const list = results.map(
|
|
650
|
+
(r) => `- [${r.entry.id.slice(0, 8)}] ${r.entry.text.slice(0, 60)}${r.entry.text.length > 60 ? "..." : ""}`
|
|
651
|
+
).join("\n");
|
|
652
|
+
return {
|
|
653
|
+
content: [
|
|
654
|
+
{
|
|
655
|
+
type: "text",
|
|
656
|
+
text: `Found ${results.length} candidates. Specify memoryId to delete:
|
|
657
|
+
${list}`
|
|
658
|
+
}
|
|
659
|
+
],
|
|
660
|
+
details: {
|
|
661
|
+
action: "candidates",
|
|
662
|
+
candidates: sanitizeMemoryForSerialization(results)
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
content: [
|
|
668
|
+
{
|
|
669
|
+
type: "text",
|
|
670
|
+
text: "Provide either 'query' to search for memories or 'memoryId' to delete specific memory."
|
|
671
|
+
}
|
|
672
|
+
],
|
|
673
|
+
details: { error: "missing_param" }
|
|
674
|
+
};
|
|
675
|
+
} catch (error) {
|
|
676
|
+
return {
|
|
677
|
+
content: [
|
|
678
|
+
{
|
|
679
|
+
type: "text",
|
|
680
|
+
text: `Memory deletion failed: ${error instanceof Error ? error.message : String(error)}`
|
|
681
|
+
}
|
|
682
|
+
],
|
|
683
|
+
details: { error: "delete_failed", message: String(error) }
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
},
|
|
689
|
+
{ name: "memory_forget" }
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
function registerMemoryUpdateTool(api, context) {
|
|
693
|
+
api.registerTool(
|
|
694
|
+
(toolCtx) => {
|
|
695
|
+
const agentId = resolveAgentId(toolCtx?.agentId, context.agentId) ?? "main";
|
|
696
|
+
return {
|
|
697
|
+
name: "memory_update",
|
|
698
|
+
label: "Memory Update",
|
|
699
|
+
description: "Update an existing memory in-place. Preserves original timestamp. Use when correcting outdated info or adjusting importance/category without losing creation date.",
|
|
700
|
+
parameters: Type.Object({
|
|
701
|
+
memoryId: Type.String({
|
|
702
|
+
description: "ID of the memory to update (full UUID or 8+ char prefix)"
|
|
703
|
+
}),
|
|
704
|
+
text: Type.Optional(
|
|
705
|
+
Type.String({
|
|
706
|
+
description: "New text content (triggers re-embedding)"
|
|
707
|
+
})
|
|
708
|
+
),
|
|
709
|
+
importance: Type.Optional(
|
|
710
|
+
Type.Number({ description: "New importance score 0-1" })
|
|
711
|
+
),
|
|
712
|
+
category: Type.Optional(stringEnum(MEMORY_CATEGORIES))
|
|
713
|
+
}),
|
|
714
|
+
async execute(_toolCallId, params, _signal, _onUpdate, runtimeCtx) {
|
|
715
|
+
const { memoryId, text, importance, category } = params;
|
|
716
|
+
try {
|
|
717
|
+
if (!text && importance === void 0 && !category) {
|
|
718
|
+
return {
|
|
719
|
+
content: [
|
|
720
|
+
{
|
|
721
|
+
type: "text",
|
|
722
|
+
text: "Nothing to update. Provide at least one of: text, importance, category."
|
|
723
|
+
}
|
|
724
|
+
],
|
|
725
|
+
details: { error: "no_updates" }
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const agentId2 = resolveRuntimeAgentId(context.agentId, runtimeCtx);
|
|
729
|
+
const scopeFilter = context.scopeManager.getAccessibleScopes(agentId2);
|
|
730
|
+
let resolvedId = memoryId;
|
|
731
|
+
const uuidLike = /^[0-9a-f]{8}(-[0-9a-f]{4}){0,4}/i.test(memoryId);
|
|
732
|
+
if (!uuidLike) {
|
|
733
|
+
const results = await retrieveWithRetry(context.retriever, {
|
|
734
|
+
query: memoryId,
|
|
735
|
+
limit: 3,
|
|
736
|
+
scopeFilter
|
|
737
|
+
});
|
|
738
|
+
if (results.length === 0) {
|
|
739
|
+
return {
|
|
740
|
+
content: [
|
|
741
|
+
{
|
|
742
|
+
type: "text",
|
|
743
|
+
text: `No memory found matching "${memoryId}".`
|
|
744
|
+
}
|
|
745
|
+
],
|
|
746
|
+
details: { error: "not_found", query: memoryId }
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
if (results.length === 1 || results[0].score > 0.85) {
|
|
750
|
+
resolvedId = results[0].entry.id;
|
|
751
|
+
} else {
|
|
752
|
+
const list = results.map(
|
|
753
|
+
(r) => `- [${r.entry.id.slice(0, 8)}] ${r.entry.text.slice(0, 60)}${r.entry.text.length > 60 ? "..." : ""}`
|
|
754
|
+
).join("\n");
|
|
755
|
+
return {
|
|
756
|
+
content: [
|
|
757
|
+
{
|
|
758
|
+
type: "text",
|
|
759
|
+
text: `Multiple matches. Specify memoryId:
|
|
760
|
+
${list}`
|
|
761
|
+
}
|
|
762
|
+
],
|
|
763
|
+
details: {
|
|
764
|
+
action: "candidates",
|
|
765
|
+
candidates: sanitizeMemoryForSerialization(results)
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
let newVector;
|
|
771
|
+
if (text) {
|
|
772
|
+
if (isNoise(text)) {
|
|
773
|
+
return {
|
|
774
|
+
content: [
|
|
775
|
+
{
|
|
776
|
+
type: "text",
|
|
777
|
+
text: "Skipped: updated text detected as noise"
|
|
778
|
+
}
|
|
779
|
+
],
|
|
780
|
+
details: { action: "noise_filtered" }
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
newVector = await context.embedder.embedPassage(text);
|
|
784
|
+
}
|
|
785
|
+
const updates = {};
|
|
786
|
+
if (text) updates.text = text;
|
|
787
|
+
if (newVector) updates.vector = newVector;
|
|
788
|
+
if (importance !== void 0)
|
|
789
|
+
updates.importance = clamp01(importance, 0.7);
|
|
790
|
+
if (category) updates.category = category;
|
|
791
|
+
const updated = await context.store.update(
|
|
792
|
+
resolvedId,
|
|
793
|
+
updates,
|
|
794
|
+
scopeFilter
|
|
795
|
+
);
|
|
796
|
+
if (!updated) {
|
|
797
|
+
return {
|
|
798
|
+
content: [
|
|
799
|
+
{
|
|
800
|
+
type: "text",
|
|
801
|
+
text: `Memory ${resolvedId.slice(0, 8)}... not found or access denied.`
|
|
802
|
+
}
|
|
803
|
+
],
|
|
804
|
+
details: { error: "not_found", id: resolvedId }
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
content: [
|
|
809
|
+
{
|
|
810
|
+
type: "text",
|
|
811
|
+
text: `Updated memory ${updated.id.slice(0, 8)}...: "${updated.text.slice(0, 80)}${updated.text.length > 80 ? "..." : ""}"`
|
|
812
|
+
}
|
|
813
|
+
],
|
|
814
|
+
details: {
|
|
815
|
+
action: "updated",
|
|
816
|
+
id: updated.id,
|
|
817
|
+
scope: updated.scope,
|
|
818
|
+
category: updated.category,
|
|
819
|
+
importance: updated.importance,
|
|
820
|
+
fieldsUpdated: Object.keys(updates)
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
} catch (error) {
|
|
824
|
+
return {
|
|
825
|
+
content: [
|
|
826
|
+
{
|
|
827
|
+
type: "text",
|
|
828
|
+
text: `Memory update failed: ${error instanceof Error ? error.message : String(error)}`
|
|
829
|
+
}
|
|
830
|
+
],
|
|
831
|
+
details: { error: "update_failed", message: String(error) }
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
},
|
|
837
|
+
{ name: "memory_update" }
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
function registerMemoryStatsTool(api, context) {
|
|
841
|
+
api.registerTool(
|
|
842
|
+
(toolCtx) => {
|
|
843
|
+
const agentId = resolveAgentId(toolCtx?.agentId, context.agentId) ?? "main";
|
|
844
|
+
return {
|
|
845
|
+
name: "memory_stats",
|
|
846
|
+
label: "Memory Statistics",
|
|
847
|
+
description: "Get statistics about memory usage, scopes, and categories.",
|
|
848
|
+
parameters: Type.Object({
|
|
849
|
+
scope: Type.Optional(
|
|
850
|
+
Type.String({
|
|
851
|
+
description: "Specific scope to get stats for (optional)"
|
|
852
|
+
})
|
|
853
|
+
)
|
|
854
|
+
}),
|
|
855
|
+
async execute(_toolCallId, params, _signal, _onUpdate, runtimeCtx) {
|
|
856
|
+
const { scope } = params;
|
|
857
|
+
try {
|
|
858
|
+
const agentId2 = resolveRuntimeAgentId(context.agentId, runtimeCtx);
|
|
859
|
+
let scopeFilter = context.scopeManager.getAccessibleScopes(agentId2);
|
|
860
|
+
if (scope) {
|
|
861
|
+
if (context.scopeManager.isAccessible(scope, agentId2)) {
|
|
862
|
+
scopeFilter = [scope];
|
|
863
|
+
} else {
|
|
864
|
+
return {
|
|
865
|
+
content: [
|
|
866
|
+
{ type: "text", text: `Access denied to scope: ${scope}` }
|
|
867
|
+
],
|
|
868
|
+
details: {
|
|
869
|
+
error: "scope_access_denied",
|
|
870
|
+
requestedScope: scope
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
const stats = await context.store.stats(scopeFilter);
|
|
876
|
+
const scopeManagerStats = context.scopeManager.getStats();
|
|
877
|
+
const retrievalConfig = context.retriever.getConfig();
|
|
878
|
+
const text = [
|
|
879
|
+
`Memory Statistics:`,
|
|
880
|
+
`\u2022 Total memories: ${stats.totalCount}`,
|
|
881
|
+
`\u2022 Available scopes: ${scopeManagerStats.totalScopes}`,
|
|
882
|
+
`\u2022 Retrieval mode: ${retrievalConfig.mode}`,
|
|
883
|
+
`\u2022 FTS support: ${context.store.hasFtsSupport ? "Yes" : "No"}`,
|
|
884
|
+
``,
|
|
885
|
+
`Memories by scope:`,
|
|
886
|
+
...Object.entries(stats.scopeCounts).map(
|
|
887
|
+
([s, count]) => ` \u2022 ${s}: ${count}`
|
|
888
|
+
),
|
|
889
|
+
``,
|
|
890
|
+
`Memories by category:`,
|
|
891
|
+
...Object.entries(stats.categoryCounts).map(
|
|
892
|
+
([c, count]) => ` \u2022 ${c}: ${count}`
|
|
893
|
+
)
|
|
894
|
+
].join("\n");
|
|
895
|
+
return {
|
|
896
|
+
content: [{ type: "text", text }],
|
|
897
|
+
details: {
|
|
898
|
+
stats,
|
|
899
|
+
scopeManagerStats,
|
|
900
|
+
retrievalConfig: {
|
|
901
|
+
...retrievalConfig,
|
|
902
|
+
rerankApiKey: retrievalConfig.rerankApiKey ? "***" : void 0
|
|
903
|
+
},
|
|
904
|
+
hasFtsSupport: context.store.hasFtsSupport
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
} catch (error) {
|
|
908
|
+
return {
|
|
909
|
+
content: [
|
|
910
|
+
{
|
|
911
|
+
type: "text",
|
|
912
|
+
text: `Failed to get memory stats: ${error instanceof Error ? error.message : String(error)}`
|
|
913
|
+
}
|
|
914
|
+
],
|
|
915
|
+
details: { error: "stats_failed", message: String(error) }
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
},
|
|
921
|
+
{ name: "memory_stats" }
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
function registerMemoryListTool(api, context) {
|
|
925
|
+
api.registerTool(
|
|
926
|
+
(toolCtx) => {
|
|
927
|
+
const agentId = resolveAgentId(toolCtx?.agentId, context.agentId) ?? "main";
|
|
928
|
+
return {
|
|
929
|
+
name: "memory_list",
|
|
930
|
+
label: "Memory List",
|
|
931
|
+
description: "List recent memories with optional filtering by scope and category.",
|
|
932
|
+
parameters: Type.Object({
|
|
933
|
+
limit: Type.Optional(
|
|
934
|
+
Type.Number({
|
|
935
|
+
description: "Max memories to list (default: 10, max: 50)"
|
|
936
|
+
})
|
|
937
|
+
),
|
|
938
|
+
scope: Type.Optional(
|
|
939
|
+
Type.String({ description: "Filter by specific scope (optional)" })
|
|
940
|
+
),
|
|
941
|
+
category: Type.Optional(stringEnum(MEMORY_CATEGORIES)),
|
|
942
|
+
offset: Type.Optional(
|
|
943
|
+
Type.Number({
|
|
944
|
+
description: "Number of memories to skip (default: 0)"
|
|
945
|
+
})
|
|
946
|
+
)
|
|
947
|
+
}),
|
|
948
|
+
async execute(_toolCallId, params, _signal, _onUpdate, runtimeCtx) {
|
|
949
|
+
const {
|
|
950
|
+
limit = 10,
|
|
951
|
+
scope,
|
|
952
|
+
category,
|
|
953
|
+
offset = 0
|
|
954
|
+
} = params;
|
|
955
|
+
try {
|
|
956
|
+
const safeLimit = clampInt(limit, 1, 50);
|
|
957
|
+
const safeOffset = clampInt(offset, 0, 1e3);
|
|
958
|
+
const agentId2 = resolveRuntimeAgentId(context.agentId, runtimeCtx);
|
|
959
|
+
let scopeFilter = context.scopeManager.getAccessibleScopes(agentId2);
|
|
960
|
+
if (scope) {
|
|
961
|
+
if (context.scopeManager.isAccessible(scope, agentId2)) {
|
|
962
|
+
scopeFilter = [scope];
|
|
963
|
+
} else {
|
|
964
|
+
return {
|
|
965
|
+
content: [
|
|
966
|
+
{ type: "text", text: `Access denied to scope: ${scope}` }
|
|
967
|
+
],
|
|
968
|
+
details: {
|
|
969
|
+
error: "scope_access_denied",
|
|
970
|
+
requestedScope: scope
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
const entries = await context.store.list(
|
|
976
|
+
scopeFilter,
|
|
977
|
+
category,
|
|
978
|
+
safeLimit,
|
|
979
|
+
safeOffset
|
|
980
|
+
);
|
|
981
|
+
if (entries.length === 0) {
|
|
982
|
+
return {
|
|
983
|
+
content: [{ type: "text", text: "No memories found." }],
|
|
984
|
+
details: {
|
|
985
|
+
count: 0,
|
|
986
|
+
filters: {
|
|
987
|
+
scope,
|
|
988
|
+
category,
|
|
989
|
+
limit: safeLimit,
|
|
990
|
+
offset: safeOffset
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
const text = entries.map((entry, i) => {
|
|
996
|
+
const date = new Date(entry.timestamp).toISOString().split("T")[0];
|
|
997
|
+
const categoryTag = getDisplayCategoryTag(entry);
|
|
998
|
+
return `${safeOffset + i + 1}. [${entry.id}] [${categoryTag}] ${entry.text.slice(0, 100)}${entry.text.length > 100 ? "..." : ""} (${date})`;
|
|
999
|
+
}).join("\n");
|
|
1000
|
+
return {
|
|
1001
|
+
content: [
|
|
1002
|
+
{
|
|
1003
|
+
type: "text",
|
|
1004
|
+
text: `Recent memories (showing ${entries.length}):
|
|
1005
|
+
|
|
1006
|
+
${text}`
|
|
1007
|
+
}
|
|
1008
|
+
],
|
|
1009
|
+
details: {
|
|
1010
|
+
count: entries.length,
|
|
1011
|
+
memories: entries.map((e) => ({
|
|
1012
|
+
id: e.id,
|
|
1013
|
+
text: e.text,
|
|
1014
|
+
category: getDisplayCategoryTag(e),
|
|
1015
|
+
rawCategory: e.category,
|
|
1016
|
+
scope: e.scope,
|
|
1017
|
+
importance: e.importance,
|
|
1018
|
+
timestamp: e.timestamp
|
|
1019
|
+
})),
|
|
1020
|
+
filters: {
|
|
1021
|
+
scope,
|
|
1022
|
+
category,
|
|
1023
|
+
limit: safeLimit,
|
|
1024
|
+
offset: safeOffset
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
return {
|
|
1030
|
+
content: [
|
|
1031
|
+
{
|
|
1032
|
+
type: "text",
|
|
1033
|
+
text: `Failed to list memories: ${error instanceof Error ? error.message : String(error)}`
|
|
1034
|
+
}
|
|
1035
|
+
],
|
|
1036
|
+
details: { error: "list_failed", message: String(error) }
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
},
|
|
1042
|
+
{ name: "memory_list" }
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
function registerAllMemoryTools(api, context, options = {}) {
|
|
1046
|
+
registerMemoryRecallTool(api, context);
|
|
1047
|
+
registerMemoryStoreTool(api, context);
|
|
1048
|
+
registerMemoryForgetTool(api, context);
|
|
1049
|
+
registerMemoryUpdateTool(api, context);
|
|
1050
|
+
if (options.enableManagementTools) {
|
|
1051
|
+
registerMemoryStatsTool(api, context);
|
|
1052
|
+
registerMemoryListTool(api, context);
|
|
1053
|
+
}
|
|
1054
|
+
if (options.enableSelfImprovementTools !== false) {
|
|
1055
|
+
registerSelfImprovementLogTool(api, context);
|
|
1056
|
+
if (options.enableManagementTools) {
|
|
1057
|
+
registerSelfImprovementExtractSkillTool(api, context);
|
|
1058
|
+
registerSelfImprovementReviewTool(api, context);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
export {
|
|
1063
|
+
MEMORY_CATEGORIES,
|
|
1064
|
+
registerAllMemoryTools,
|
|
1065
|
+
registerMemoryForgetTool,
|
|
1066
|
+
registerMemoryListTool,
|
|
1067
|
+
registerMemoryRecallTool,
|
|
1068
|
+
registerMemoryStatsTool,
|
|
1069
|
+
registerMemoryStoreTool,
|
|
1070
|
+
registerMemoryUpdateTool,
|
|
1071
|
+
registerSelfImprovementExtractSkillTool,
|
|
1072
|
+
registerSelfImprovementLogTool,
|
|
1073
|
+
registerSelfImprovementReviewTool
|
|
1074
|
+
};
|
|
1075
|
+
//# sourceMappingURL=tools.js.map
|