@crowley/rag-mcp 1.3.0 → 1.5.0
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.
|
@@ -19,7 +19,13 @@ export declare const DEFAULT_ENRICHABLE_TOOLS: Set<string>;
|
|
|
19
19
|
export declare const DEFAULT_SKIP_TOOLS: Set<string>;
|
|
20
20
|
export declare class ContextEnricher {
|
|
21
21
|
private config;
|
|
22
|
+
private cache;
|
|
23
|
+
private static CACHE_TTL_MS;
|
|
22
24
|
constructor(config?: Partial<EnrichmentConfig>);
|
|
25
|
+
/**
|
|
26
|
+
* Clear enrichment cache (call on session end).
|
|
27
|
+
*/
|
|
28
|
+
clearCache(): void;
|
|
23
29
|
/**
|
|
24
30
|
* Before hook: auto-recall relevant memories/patterns/ADRs.
|
|
25
31
|
* Returns a context prefix string or null if nothing relevant found.
|
|
@@ -45,6 +45,8 @@ export const DEFAULT_SKIP_TOOLS = new Set([
|
|
|
45
45
|
]);
|
|
46
46
|
export class ContextEnricher {
|
|
47
47
|
config;
|
|
48
|
+
cache = new Map();
|
|
49
|
+
static CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
48
50
|
constructor(config = {}) {
|
|
49
51
|
this.config = {
|
|
50
52
|
enrichableTools: config.enrichableTools ?? DEFAULT_ENRICHABLE_TOOLS,
|
|
@@ -54,6 +56,12 @@ export class ContextEnricher {
|
|
|
54
56
|
timeoutMs: config.timeoutMs ?? 2000,
|
|
55
57
|
};
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Clear enrichment cache (call on session end).
|
|
61
|
+
*/
|
|
62
|
+
clearCache() {
|
|
63
|
+
this.cache.clear();
|
|
64
|
+
}
|
|
57
65
|
/**
|
|
58
66
|
* Before hook: auto-recall relevant memories/patterns/ADRs.
|
|
59
67
|
* Returns a context prefix string or null if nothing relevant found.
|
|
@@ -68,11 +76,29 @@ export class ContextEnricher {
|
|
|
68
76
|
const query = this.extractQuery(args);
|
|
69
77
|
if (!query)
|
|
70
78
|
return null;
|
|
79
|
+
// Check per-session cache
|
|
80
|
+
const cacheKey = `${ctx.activeSessionId || 'no-session'}:${query.slice(0, 100)}`;
|
|
81
|
+
const cached = this.cache.get(cacheKey);
|
|
82
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
83
|
+
return cached.result;
|
|
84
|
+
}
|
|
71
85
|
try {
|
|
72
86
|
const memories = await this.recallWithTimeout(query, ctx);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
const result = memories.length === 0 ? null : this.formatContext(memories);
|
|
88
|
+
// Store in cache
|
|
89
|
+
this.cache.set(cacheKey, {
|
|
90
|
+
result,
|
|
91
|
+
expiresAt: Date.now() + ContextEnricher.CACHE_TTL_MS,
|
|
92
|
+
});
|
|
93
|
+
// Evict expired entries lazily (every 50 calls)
|
|
94
|
+
if (this.cache.size > 100) {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
for (const [key, entry] of this.cache) {
|
|
97
|
+
if (now > entry.expiresAt)
|
|
98
|
+
this.cache.delete(key);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
76
102
|
}
|
|
77
103
|
catch {
|
|
78
104
|
// Enrichment should never break tool calls
|
|
@@ -150,8 +176,10 @@ export class ContextEnricher {
|
|
|
150
176
|
const controller = new AbortController();
|
|
151
177
|
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
152
178
|
try {
|
|
153
|
-
// Parallel recall: general memories + decisions/ADRs
|
|
154
|
-
const
|
|
179
|
+
// Parallel recall: general memories + decisions/ADRs + LTM (when enabled)
|
|
180
|
+
const graphRecallEnabled = process.env.GRAPH_RECALL_ENABLED === "true";
|
|
181
|
+
const consolidationEnabled = process.env.CONSOLIDATION_ENABLED === "true";
|
|
182
|
+
const recalls = [
|
|
155
183
|
ctx.api
|
|
156
184
|
.post("/api/memory/recall-durable", {
|
|
157
185
|
projectName: ctx.projectName,
|
|
@@ -168,7 +196,19 @@ export class ContextEnricher {
|
|
|
168
196
|
type: "decision",
|
|
169
197
|
}, { signal: controller.signal })
|
|
170
198
|
.catch(() => null),
|
|
171
|
-
]
|
|
199
|
+
];
|
|
200
|
+
// Phase 2+4: also recall from LTM (episodic+semantic with Ebbinghaus decay)
|
|
201
|
+
if (consolidationEnabled) {
|
|
202
|
+
recalls.push(ctx.api
|
|
203
|
+
.post("/api/memory/recall-ltm", {
|
|
204
|
+
projectName: ctx.projectName,
|
|
205
|
+
query,
|
|
206
|
+
limit: this.config.maxAutoRecall,
|
|
207
|
+
graphRecall: graphRecallEnabled,
|
|
208
|
+
}, { signal: controller.signal })
|
|
209
|
+
.catch(() => null));
|
|
210
|
+
}
|
|
211
|
+
const [memoriesRes, decisionsRes, ltmRes] = await Promise.all(recalls);
|
|
172
212
|
const memories = [];
|
|
173
213
|
const seenIds = new Set();
|
|
174
214
|
// Process general memories
|
|
@@ -197,6 +237,23 @@ export class ContextEnricher {
|
|
|
197
237
|
}
|
|
198
238
|
}
|
|
199
239
|
}
|
|
240
|
+
// Process LTM results (episodic + semantic)
|
|
241
|
+
if (ltmRes?.data?.results) {
|
|
242
|
+
for (const r of ltmRes.data.results) {
|
|
243
|
+
const mem = r.memory;
|
|
244
|
+
const id = mem?.id;
|
|
245
|
+
if (r.score >= this.config.minRelevance && id && !seenIds.has(id)) {
|
|
246
|
+
seenIds.add(id);
|
|
247
|
+
memories.push({
|
|
248
|
+
type: mem?.subtype || mem?.type || "insight",
|
|
249
|
+
content: mem?.content || "",
|
|
250
|
+
score: r.score,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Sort by score and limit
|
|
256
|
+
memories.sort((a, b) => b.score - a.score);
|
|
200
257
|
return memories.slice(0, this.config.maxAutoRecall + 2);
|
|
201
258
|
}
|
|
202
259
|
finally {
|
package/dist/tool-middleware.js
CHANGED
|
@@ -155,6 +155,43 @@ export function trackUsage(name, args, startTime, success, result, errorMessage,
|
|
|
155
155
|
})
|
|
156
156
|
.catch(() => { });
|
|
157
157
|
}
|
|
158
|
+
// ── Sensory buffer ─────────────────────────────────────────
|
|
159
|
+
/** Extract file paths from tool args */
|
|
160
|
+
function extractFiles(args) {
|
|
161
|
+
const files = [];
|
|
162
|
+
for (const key of ['file', 'filePath', 'currentFile', 'path']) {
|
|
163
|
+
const v = args[key];
|
|
164
|
+
if (typeof v === 'string' && v.length > 0)
|
|
165
|
+
files.push(v);
|
|
166
|
+
}
|
|
167
|
+
const arr = args.affectedFiles || args.files;
|
|
168
|
+
if (Array.isArray(arr)) {
|
|
169
|
+
for (const f of arr) {
|
|
170
|
+
if (typeof f === 'string')
|
|
171
|
+
files.push(f);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return files.slice(0, 20);
|
|
175
|
+
}
|
|
176
|
+
/** Fire-and-forget: capture tool event in sensory buffer */
|
|
177
|
+
function appendToSensoryBuffer(name, args, startTime, success, resultText, ctx) {
|
|
178
|
+
if (!ctx.activeSessionId)
|
|
179
|
+
return;
|
|
180
|
+
if (TRACKING_EXCLUDE.has(name))
|
|
181
|
+
return;
|
|
182
|
+
ctx.api
|
|
183
|
+
.post("/api/sensory/append", {
|
|
184
|
+
projectName: ctx.projectName,
|
|
185
|
+
sessionId: ctx.activeSessionId,
|
|
186
|
+
toolName: name,
|
|
187
|
+
inputSummary: summarizeInput(name, args).slice(0, 500),
|
|
188
|
+
outputSummary: (resultText || "").slice(0, 500),
|
|
189
|
+
filesTouched: extractFiles(args),
|
|
190
|
+
success,
|
|
191
|
+
durationMs: Date.now() - startTime,
|
|
192
|
+
})
|
|
193
|
+
.catch(() => { });
|
|
194
|
+
}
|
|
158
195
|
// ── Error formatting ────────────────────────────────────────
|
|
159
196
|
/** Format an error caught during tool execution */
|
|
160
197
|
export function formatToolError(error, ctx) {
|
|
@@ -207,6 +244,8 @@ export function wrapHandler(name, handler, deps) {
|
|
|
207
244
|
}
|
|
208
245
|
// Track usage (fire-and-forget)
|
|
209
246
|
trackUsage(name, args, startTime, true, text, undefined, ctx);
|
|
247
|
+
// Capture in sensory buffer (fire-and-forget)
|
|
248
|
+
appendToSensoryBuffer(name, args, startTime, true, text, ctx);
|
|
210
249
|
// Prepend context/warnings if available
|
|
211
250
|
const prefix = [warningPrefix, contextPrefix].filter(Boolean).join('');
|
|
212
251
|
if (prefix) {
|
|
@@ -221,6 +260,8 @@ export function wrapHandler(name, handler, deps) {
|
|
|
221
260
|
const errorMessage = formatToolError(error, ctx);
|
|
222
261
|
// Track failed usage (fire-and-forget)
|
|
223
262
|
trackUsage(name, args, startTime, false, "", errorMessage, ctx);
|
|
263
|
+
// Capture failure in sensory buffer (fire-and-forget)
|
|
264
|
+
appendToSensoryBuffer(name, args, startTime, false, errorMessage, ctx);
|
|
224
265
|
return errorMessage;
|
|
225
266
|
}
|
|
226
267
|
};
|
package/dist/tools/memory.js
CHANGED
|
@@ -61,19 +61,22 @@ export function createMemoryTools(projectName) {
|
|
|
61
61
|
description: "Retrieve relevant memories based on context. Searches agent memory for past decisions, insights, and notes related to the query.",
|
|
62
62
|
schema: z.object({
|
|
63
63
|
query: z.string().describe("What to recall (semantic search)"),
|
|
64
|
-
type: z.enum(["decision", "insight", "context", "todo", "conversation", "note", "all"]).optional().describe("Filter by memory type (default: all)"),
|
|
64
|
+
type: z.enum(["decision", "insight", "context", "todo", "conversation", "note", "procedure", "all"]).optional().describe("Filter by memory type (default: all)"),
|
|
65
65
|
limit: z.coerce.number().optional().describe("Max memories to retrieve (default: 5)"),
|
|
66
|
+
graphRecall: z.boolean().optional().describe("Enable graph-aware recall with spreading activation (default: false)"),
|
|
66
67
|
}),
|
|
67
68
|
annotations: TOOL_ANNOTATIONS["recall"],
|
|
68
69
|
handler: async (args, ctx) => {
|
|
69
70
|
const query = args.query;
|
|
70
71
|
const type = args.type || "all";
|
|
71
72
|
const limit = args.limit || 5;
|
|
73
|
+
const graphRecall = args.graphRecall || false;
|
|
72
74
|
const response = await ctx.api.post("/api/memory/recall", {
|
|
73
75
|
projectName: ctx.projectName,
|
|
74
76
|
query,
|
|
75
77
|
type,
|
|
76
78
|
limit,
|
|
79
|
+
graphRecall,
|
|
77
80
|
});
|
|
78
81
|
const results = response.data.results || [];
|
|
79
82
|
if (results.length === 0) {
|