@memoryrelay/plugin-memoryrelay-ai 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +87 -3579
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -4
- package/src/debug-logger.ts +0 -169
- package/src/status-reporter.ts +0 -284
package/index.ts
CHANGED
|
@@ -1,873 +1,137 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenClaw Memory Plugin - MemoryRelay
|
|
3
|
-
* Version: 0.9.
|
|
4
|
-
*
|
|
5
|
-
* Long-term memory with vector search using MemoryRelay API.
|
|
6
|
-
* Provides auto-recall and auto-capture via lifecycle hooks.
|
|
7
|
-
* Includes: memories, entities, agents, sessions, decisions, patterns, projects.
|
|
8
|
-
*
|
|
9
|
-
* API: https://api.memoryrelay.net
|
|
10
|
-
* Docs: https://memoryrelay.ai
|
|
11
|
-
*
|
|
12
|
-
* ENHANCEMENTS (v0.9.3):
|
|
13
|
-
* - Removed fs.writeFile from export command (stdout only now)
|
|
14
|
-
* - No filesystem operations - passes OpenClaw security validation
|
|
15
|
-
* - Export usage: openclaw memoryrelay export > memories.json
|
|
16
|
-
*
|
|
17
|
-
* ENHANCEMENTS (v0.8.4):
|
|
18
|
-
* - Removed file logging feature to pass OpenClaw security validation
|
|
19
|
-
* - All debug logs now in-memory only (circular buffer)
|
|
20
|
-
* - logFile config option deprecated (ignored with warning)
|
|
21
|
-
* - Clean npm installation without security warnings
|
|
22
|
-
* - Gateway methods for log access coming in v0.9.3
|
|
23
|
-
*
|
|
24
|
-
* ENHANCEMENTS (v0.8.3):
|
|
25
|
-
* - Security fix: logFile restricted to relative paths
|
|
26
|
-
* - Path validation (reject absolute paths and traversal)
|
|
27
|
-
*
|
|
28
|
-
* ENHANCEMENTS (v0.8.2):
|
|
29
|
-
* - Human-readable gateway logs with memory previews
|
|
30
|
-
* - Show similarity scores and memory snippets during auto-recall
|
|
31
|
-
* - Performance indicators (✓/✗ and timing with SLOW warnings)
|
|
32
|
-
* - Cleaner error messages in gateway logs
|
|
33
|
-
*
|
|
34
|
-
* ENHANCEMENTS (v0.8.0):
|
|
35
|
-
* - Debug mode with comprehensive API call logging
|
|
36
|
-
* - Enhanced status reporting with tool breakdown
|
|
37
|
-
* - Request/response capture (verbose mode)
|
|
38
|
-
* - Tool failure tracking and known issues display
|
|
39
|
-
* - Performance metrics (duration, success rate)
|
|
40
|
-
* - Recent activity display
|
|
41
|
-
* - Formatted CLI output with Unicode symbols
|
|
42
|
-
*
|
|
43
|
-
* ENHANCEMENTS (v0.7.0):
|
|
44
|
-
* - 39 tools covering all MemoryRelay API resources
|
|
45
|
-
* - Session tracking, decision logging, pattern management, project context
|
|
46
|
-
* - Agent workflow instructions injected via before_agent_start
|
|
47
|
-
* - Retry logic with exponential backoff (3 attempts)
|
|
48
|
-
* - Request timeout (30 seconds)
|
|
49
|
-
* - Environment variable fallback support
|
|
50
|
-
* - Channel filtering (excludeChannels config)
|
|
51
|
-
* - Additional CLI commands (stats, delete, export)
|
|
2
|
+
* OpenClaw Memory Plugin - MemoryRelay (Single File Version)
|
|
3
|
+
* Version: 0.9.5
|
|
52
4
|
*/
|
|
53
5
|
|
|
54
6
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
55
|
-
import { DebugLogger, type LogEntry } from "./src/debug-logger.js";
|
|
56
|
-
import { StatusReporter } from "./src/status-reporter.js";
|
|
57
7
|
|
|
58
8
|
// ============================================================================
|
|
59
|
-
//
|
|
9
|
+
// DebugLogger (Inlined)
|
|
60
10
|
// ============================================================================
|
|
61
11
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
agentId?: string;
|
|
75
|
-
apiUrl?: string;
|
|
76
|
-
autoCapture?: boolean;
|
|
77
|
-
autoRecall?: boolean;
|
|
78
|
-
recallLimit?: number;
|
|
79
|
-
recallThreshold?: number;
|
|
80
|
-
excludeChannels?: string[];
|
|
81
|
-
defaultProject?: string;
|
|
82
|
-
enabledTools?: string;
|
|
83
|
-
// Debug and logging options (v0.8.0)
|
|
84
|
-
debug?: boolean;
|
|
85
|
-
verbose?: boolean;
|
|
86
|
-
logFile?: string;
|
|
87
|
-
maxLogEntries?: number;
|
|
12
|
+
interface LogEntry {
|
|
13
|
+
timestamp: string;
|
|
14
|
+
tool: string;
|
|
15
|
+
method: string;
|
|
16
|
+
path: string;
|
|
17
|
+
duration: number;
|
|
18
|
+
status: "success" | "error";
|
|
19
|
+
requestBody?: unknown;
|
|
20
|
+
responseBody?: unknown;
|
|
21
|
+
responseStatus?: number;
|
|
22
|
+
error?: string;
|
|
23
|
+
retries?: number;
|
|
88
24
|
}
|
|
89
25
|
|
|
90
|
-
interface
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
user_id: string;
|
|
95
|
-
metadata: Record<string, string>;
|
|
96
|
-
entities: string[];
|
|
97
|
-
created_at: number;
|
|
98
|
-
updated_at: number;
|
|
26
|
+
interface DebugLoggerConfig {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
verbose: boolean;
|
|
29
|
+
maxEntries: number;
|
|
99
30
|
}
|
|
100
31
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
32
|
+
class DebugLogger {
|
|
33
|
+
private logs: LogEntry[] = [];
|
|
34
|
+
private config: DebugLoggerConfig;
|
|
105
35
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
last_updated?: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ============================================================================
|
|
112
|
-
// Utility Functions
|
|
113
|
-
// ============================================================================
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Sleep for specified milliseconds
|
|
117
|
-
*/
|
|
118
|
-
function sleep(ms: number): Promise<void> {
|
|
119
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check if error is retryable (network/timeout errors)
|
|
124
|
-
*/
|
|
125
|
-
function isRetryableError(error: unknown): boolean {
|
|
126
|
-
const errStr = String(error).toLowerCase();
|
|
127
|
-
return (
|
|
128
|
-
errStr.includes("timeout") ||
|
|
129
|
-
errStr.includes("econnrefused") ||
|
|
130
|
-
errStr.includes("enotfound") ||
|
|
131
|
-
errStr.includes("network") ||
|
|
132
|
-
errStr.includes("fetch failed") ||
|
|
133
|
-
errStr.includes("502") ||
|
|
134
|
-
errStr.includes("503") ||
|
|
135
|
-
errStr.includes("504")
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Fetch with timeout
|
|
141
|
-
*/
|
|
142
|
-
async function fetchWithTimeout(
|
|
143
|
-
url: string,
|
|
144
|
-
options: RequestInit,
|
|
145
|
-
timeoutMs: number,
|
|
146
|
-
): Promise<Response> {
|
|
147
|
-
const controller = new AbortController();
|
|
148
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const response = await fetch(url, {
|
|
152
|
-
...options,
|
|
153
|
-
signal: controller.signal,
|
|
154
|
-
});
|
|
155
|
-
clearTimeout(timeout);
|
|
156
|
-
return response;
|
|
157
|
-
} catch (err) {
|
|
158
|
-
clearTimeout(timeout);
|
|
159
|
-
if ((err as Error).name === "AbortError") {
|
|
160
|
-
throw new Error("Request timeout");
|
|
161
|
-
}
|
|
162
|
-
throw err;
|
|
36
|
+
constructor(config: DebugLoggerConfig) {
|
|
37
|
+
this.config = config;
|
|
163
38
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ============================================================================
|
|
167
|
-
// MemoryRelay API Client (Full Suite)
|
|
168
|
-
// ============================================================================
|
|
169
|
-
|
|
170
|
-
class MemoryRelayClient {
|
|
171
|
-
private debugLogger?: DebugLogger;
|
|
172
|
-
private statusReporter?: StatusReporter;
|
|
173
|
-
private config?: MemoryRelayConfig;
|
|
174
|
-
private api?: OpenClawPluginApi;
|
|
175
|
-
|
|
176
|
-
constructor(
|
|
177
|
-
private readonly apiKey: string,
|
|
178
|
-
private readonly agentId: string,
|
|
179
|
-
private readonly apiUrl: string = DEFAULT_API_URL,
|
|
180
|
-
debugLogger?: DebugLogger,
|
|
181
|
-
statusReporter?: StatusReporter,
|
|
182
|
-
api?: OpenClawPluginApi,
|
|
183
|
-
) {
|
|
184
|
-
this.debugLogger = debugLogger;
|
|
185
|
-
this.statusReporter = statusReporter;
|
|
186
|
-
this.api = api;
|
|
187
|
-
this.config = api?.pluginConfig as MemoryRelayConfig | undefined;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Extract tool name from API path
|
|
192
|
-
*/
|
|
193
|
-
private extractToolName(path: string): string {
|
|
194
|
-
// /v1/memories -> memory
|
|
195
|
-
// /v1/memories/batch -> memory_batch
|
|
196
|
-
// /v1/sessions/123/end -> session_end
|
|
197
|
-
const parts = path.split("/").filter(Boolean);
|
|
198
|
-
if (parts.length < 2) return "unknown";
|
|
199
|
-
|
|
200
|
-
let toolName = parts[1].replace(/s$/, ""); // Remove trailing 's'
|
|
201
|
-
|
|
202
|
-
// Check for specific endpoints
|
|
203
|
-
if (path.includes("/batch")) toolName += "_batch";
|
|
204
|
-
if (path.includes("/recall")) toolName += "_recall";
|
|
205
|
-
if (path.includes("/context")) toolName += "_context";
|
|
206
|
-
if (path.includes("/end")) toolName += "_end";
|
|
207
|
-
if (path.includes("/health")) return "memory_health";
|
|
208
|
-
|
|
209
|
-
return toolName;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Make HTTP request with retry logic and timeout
|
|
214
|
-
*/
|
|
215
|
-
private async request<T>(
|
|
216
|
-
method: string,
|
|
217
|
-
path: string,
|
|
218
|
-
body?: unknown,
|
|
219
|
-
retryCount = 0,
|
|
220
|
-
): Promise<T> {
|
|
221
|
-
const url = `${this.apiUrl}${path}`;
|
|
222
|
-
const startTime = Date.now();
|
|
223
|
-
const toolName = this.extractToolName(path);
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const response = await fetchWithTimeout(
|
|
227
|
-
url,
|
|
228
|
-
{
|
|
229
|
-
method,
|
|
230
|
-
headers: {
|
|
231
|
-
"Content-Type": "application/json",
|
|
232
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
233
|
-
"User-Agent": "openclaw-memory-memoryrelay/0.8.0",
|
|
234
|
-
},
|
|
235
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
236
|
-
},
|
|
237
|
-
REQUEST_TIMEOUT_MS,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
const duration = Date.now() - startTime;
|
|
241
|
-
|
|
242
|
-
if (!response.ok) {
|
|
243
|
-
const errorData = await response.json().catch(() => ({}));
|
|
244
|
-
const errorMsg = errorData.detail || errorData.message || "";
|
|
245
|
-
const error = new Error(
|
|
246
|
-
`MemoryRelay API error: ${response.status} ${response.statusText}` +
|
|
247
|
-
(errorMsg ? ` - ${errorMsg}` : ""),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
// Log error
|
|
251
|
-
if (this.debugLogger) {
|
|
252
|
-
this.debugLogger.log({
|
|
253
|
-
timestamp: new Date().toISOString(),
|
|
254
|
-
tool: toolName,
|
|
255
|
-
method,
|
|
256
|
-
path,
|
|
257
|
-
duration,
|
|
258
|
-
status: "error",
|
|
259
|
-
responseStatus: response.status,
|
|
260
|
-
error: error.message,
|
|
261
|
-
retries: retryCount,
|
|
262
|
-
requestBody: this.debugLogger && body ? body : undefined,
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Enhanced gateway logging (v0.8.2): Readable error summary
|
|
266
|
-
if (this.config.debug && this.api) {
|
|
267
|
-
const retryMsg = retryCount > 0 ? ` (retry ${retryCount}/${MAX_RETRIES})` : '';
|
|
268
|
-
this.api.logger.warn?.(
|
|
269
|
-
`memory-memoryrelay: ${toolName} → ${response.status} ${errorMsg || response.statusText}${retryMsg}`
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Track failure
|
|
275
|
-
if (this.statusReporter) {
|
|
276
|
-
this.statusReporter.recordFailure(toolName, `${response.status} ${errorMsg || response.statusText}`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Retry on 5xx errors
|
|
280
|
-
if (response.status >= 500 && retryCount < MAX_RETRIES) {
|
|
281
|
-
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
282
|
-
await sleep(delay);
|
|
283
|
-
return this.request<T>(method, path, body, retryCount + 1);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const result = await response.json();
|
|
290
|
-
|
|
291
|
-
// Log success
|
|
292
|
-
if (this.debugLogger) {
|
|
293
|
-
this.debugLogger.log({
|
|
294
|
-
timestamp: new Date().toISOString(),
|
|
295
|
-
tool: toolName,
|
|
296
|
-
method,
|
|
297
|
-
path,
|
|
298
|
-
duration,
|
|
299
|
-
status: "success",
|
|
300
|
-
responseStatus: response.status,
|
|
301
|
-
retries: retryCount,
|
|
302
|
-
requestBody: this.debugLogger && body ? body : undefined,
|
|
303
|
-
responseBody: this.debugLogger && result ? result : undefined,
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// Enhanced gateway logging (v0.8.2): Readable API call summary
|
|
307
|
-
if (this.config.debug && this.api) {
|
|
308
|
-
const statusSymbol = response.status < 400 ? '✓' : '✗';
|
|
309
|
-
const durationColor = duration > 1000 ? ' (SLOW)' : duration > 500 ? ' (slow)' : '';
|
|
310
|
-
this.api.logger.info?.(
|
|
311
|
-
`memory-memoryrelay: ${toolName} → ${duration}ms ${statusSymbol}${durationColor}`
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Track success
|
|
317
|
-
if (this.statusReporter) {
|
|
318
|
-
this.statusReporter.recordSuccess(toolName);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return result;
|
|
322
|
-
} catch (err) {
|
|
323
|
-
const duration = Date.now() - startTime;
|
|
324
|
-
|
|
325
|
-
// Log error
|
|
326
|
-
if (this.debugLogger) {
|
|
327
|
-
this.debugLogger.log({
|
|
328
|
-
timestamp: new Date().toISOString(),
|
|
329
|
-
tool: toolName,
|
|
330
|
-
method,
|
|
331
|
-
path,
|
|
332
|
-
duration,
|
|
333
|
-
status: "error",
|
|
334
|
-
error: String(err),
|
|
335
|
-
retries: retryCount,
|
|
336
|
-
requestBody: this.debugLogger && body ? body : undefined,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
39
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// Retry on network errors
|
|
346
|
-
if (isRetryableError(err) && retryCount < MAX_RETRIES) {
|
|
347
|
-
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
348
|
-
await sleep(delay);
|
|
349
|
-
return this.request<T>(method, path, body, retryCount + 1);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
throw err;
|
|
40
|
+
log(entry: LogEntry): void {
|
|
41
|
+
if (!this.config.enabled) return;
|
|
42
|
+
this.logs.push(entry);
|
|
43
|
+
if (this.logs.length > this.config.maxEntries) {
|
|
44
|
+
this.logs.shift();
|
|
353
45
|
}
|
|
354
46
|
}
|
|
355
47
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// --------------------------------------------------------------------------
|
|
359
|
-
|
|
360
|
-
async store(
|
|
361
|
-
content: string,
|
|
362
|
-
metadata?: Record<string, string>,
|
|
363
|
-
options?: {
|
|
364
|
-
deduplicate?: boolean;
|
|
365
|
-
dedup_threshold?: number;
|
|
366
|
-
project?: string;
|
|
367
|
-
importance?: number;
|
|
368
|
-
tier?: string;
|
|
369
|
-
},
|
|
370
|
-
): Promise<Memory> {
|
|
371
|
-
return this.request<Memory>("POST", "/v1/memories", {
|
|
372
|
-
content,
|
|
373
|
-
metadata,
|
|
374
|
-
agent_id: this.agentId,
|
|
375
|
-
...options,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async search(
|
|
380
|
-
query: string,
|
|
381
|
-
limit: number = 5,
|
|
382
|
-
threshold: number = 0.3,
|
|
383
|
-
options?: {
|
|
384
|
-
include_confidential?: boolean;
|
|
385
|
-
include_archived?: boolean;
|
|
386
|
-
compress?: boolean;
|
|
387
|
-
max_context_tokens?: number;
|
|
388
|
-
project?: string;
|
|
389
|
-
tier?: string;
|
|
390
|
-
min_importance?: number;
|
|
391
|
-
},
|
|
392
|
-
): Promise<SearchResult[]> {
|
|
393
|
-
const response = await this.request<{ data: SearchResult[] }>(
|
|
394
|
-
"POST",
|
|
395
|
-
"/v1/memories/search",
|
|
396
|
-
{
|
|
397
|
-
query,
|
|
398
|
-
limit,
|
|
399
|
-
threshold,
|
|
400
|
-
agent_id: this.agentId,
|
|
401
|
-
...options,
|
|
402
|
-
},
|
|
403
|
-
);
|
|
404
|
-
return response.data || [];
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async list(limit: number = 20, offset: number = 0): Promise<Memory[]> {
|
|
408
|
-
const response = await this.request<{ data: Memory[] }>(
|
|
409
|
-
"GET",
|
|
410
|
-
`/v1/memories?limit=${limit}&offset=${offset}&agent_id=${encodeURIComponent(this.agentId)}`,
|
|
411
|
-
);
|
|
412
|
-
return response.data || [];
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async get(id: string): Promise<Memory> {
|
|
416
|
-
return this.request<Memory>("GET", `/v1/memories/${id}`);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
async update(id: string, content: string, metadata?: Record<string, string>): Promise<Memory> {
|
|
420
|
-
return this.request<Memory>("PUT", `/v1/memories/${id}`, {
|
|
421
|
-
content,
|
|
422
|
-
metadata,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
async delete(id: string): Promise<void> {
|
|
427
|
-
await this.request<void>("DELETE", `/v1/memories/${id}`);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
async batchStore(
|
|
431
|
-
memories: Array<{ content: string; metadata?: Record<string, string> }>,
|
|
432
|
-
): Promise<any> {
|
|
433
|
-
return this.request("POST", "/v1/memories/batch", {
|
|
434
|
-
memories,
|
|
435
|
-
agent_id: this.agentId,
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
async buildContext(
|
|
440
|
-
query: string,
|
|
441
|
-
limit?: number,
|
|
442
|
-
threshold?: number,
|
|
443
|
-
maxTokens?: number,
|
|
444
|
-
project?: string,
|
|
445
|
-
): Promise<any> {
|
|
446
|
-
return this.request("POST", "/v1/memories/context", {
|
|
447
|
-
query,
|
|
448
|
-
limit,
|
|
449
|
-
threshold,
|
|
450
|
-
max_tokens: maxTokens,
|
|
451
|
-
agent_id: this.agentId,
|
|
452
|
-
project,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
async promote(memoryId: string, importance: number, tier?: string): Promise<any> {
|
|
457
|
-
return this.request("PUT", `/v1/memories/${memoryId}/importance`, {
|
|
458
|
-
importance,
|
|
459
|
-
tier,
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// --------------------------------------------------------------------------
|
|
464
|
-
// Entity operations
|
|
465
|
-
// --------------------------------------------------------------------------
|
|
466
|
-
|
|
467
|
-
async createEntity(
|
|
468
|
-
name: string,
|
|
469
|
-
type: string,
|
|
470
|
-
metadata?: Record<string, string>,
|
|
471
|
-
): Promise<any> {
|
|
472
|
-
return this.request("POST", "/v1/entities", {
|
|
473
|
-
name,
|
|
474
|
-
type,
|
|
475
|
-
metadata,
|
|
476
|
-
agent_id: this.agentId,
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async linkEntity(
|
|
481
|
-
entityId: string,
|
|
482
|
-
memoryId: string,
|
|
483
|
-
relationship?: string,
|
|
484
|
-
): Promise<any> {
|
|
485
|
-
return this.request("POST", `/v1/entities/links`, {
|
|
486
|
-
entity_id: entityId,
|
|
487
|
-
memory_id: memoryId,
|
|
488
|
-
relationship,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
async listEntities(limit: number = 20, offset: number = 0): Promise<any> {
|
|
493
|
-
return this.request("GET", `/v1/entities?limit=${limit}&offset=${offset}`);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
async entityGraph(
|
|
497
|
-
entityId: string,
|
|
498
|
-
depth: number = 2,
|
|
499
|
-
maxNeighbors: number = 10,
|
|
500
|
-
): Promise<any> {
|
|
501
|
-
return this.request(
|
|
502
|
-
"GET",
|
|
503
|
-
`/v1/entities/${entityId}/neighborhood?depth=${depth}&max_neighbors=${maxNeighbors}`,
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// --------------------------------------------------------------------------
|
|
508
|
-
// Agent operations
|
|
509
|
-
// --------------------------------------------------------------------------
|
|
510
|
-
|
|
511
|
-
async listAgents(limit: number = 20): Promise<any> {
|
|
512
|
-
return this.request("GET", `/v1/agents?limit=${limit}`);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async createAgent(name: string, description?: string): Promise<any> {
|
|
516
|
-
return this.request("POST", "/v1/agents", { name, description });
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
async getAgent(id: string): Promise<any> {
|
|
520
|
-
return this.request("GET", `/v1/agents/${id}`);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// --------------------------------------------------------------------------
|
|
524
|
-
// Session operations
|
|
525
|
-
// --------------------------------------------------------------------------
|
|
526
|
-
|
|
527
|
-
async startSession(
|
|
528
|
-
title?: string,
|
|
529
|
-
project?: string,
|
|
530
|
-
metadata?: Record<string, string>,
|
|
531
|
-
): Promise<any> {
|
|
532
|
-
return this.request("POST", "/v1/sessions", {
|
|
533
|
-
title,
|
|
534
|
-
project,
|
|
535
|
-
metadata,
|
|
536
|
-
agent_id: this.agentId,
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
async endSession(id: string, summary?: string): Promise<any> {
|
|
541
|
-
return this.request("PUT", `/v1/sessions/${id}/end`, { summary });
|
|
48
|
+
getRecentLogs(limit: number = 10): LogEntry[] {
|
|
49
|
+
return this.logs.slice(-limit);
|
|
542
50
|
}
|
|
543
51
|
|
|
544
|
-
|
|
545
|
-
return this.
|
|
52
|
+
getToolLogs(toolName: string, limit: number = 10): LogEntry[] {
|
|
53
|
+
return this.logs.filter(log => log.tool === toolName).slice(-limit);
|
|
546
54
|
}
|
|
547
55
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
project?: string,
|
|
551
|
-
status?: string,
|
|
552
|
-
): Promise<any> {
|
|
553
|
-
let path = `/v1/sessions?limit=${limit}`;
|
|
554
|
-
if (project) path += `&project=${encodeURIComponent(project)}`;
|
|
555
|
-
if (status) path += `&status=${encodeURIComponent(status)}`;
|
|
556
|
-
return this.request("GET", path);
|
|
56
|
+
getErrorLogs(limit: number = 10): LogEntry[] {
|
|
57
|
+
return this.logs.filter(log => log.status === "error").slice(-limit);
|
|
557
58
|
}
|
|
558
59
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
// --------------------------------------------------------------------------
|
|
562
|
-
|
|
563
|
-
async recordDecision(
|
|
564
|
-
title: string,
|
|
565
|
-
rationale: string,
|
|
566
|
-
alternatives?: string,
|
|
567
|
-
project?: string,
|
|
568
|
-
tags?: string[],
|
|
569
|
-
status?: string,
|
|
570
|
-
): Promise<any> {
|
|
571
|
-
return this.request("POST", "/v1/decisions", {
|
|
572
|
-
title,
|
|
573
|
-
rationale,
|
|
574
|
-
alternatives,
|
|
575
|
-
project_slug: project,
|
|
576
|
-
tags,
|
|
577
|
-
status,
|
|
578
|
-
agent_id: this.agentId,
|
|
579
|
-
});
|
|
60
|
+
getAllLogs(): LogEntry[] {
|
|
61
|
+
return [...this.logs];
|
|
580
62
|
}
|
|
581
63
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
project?: string,
|
|
585
|
-
status?: string,
|
|
586
|
-
tags?: string,
|
|
587
|
-
): Promise<any> {
|
|
588
|
-
let path = `/v1/decisions?limit=${limit}`;
|
|
589
|
-
if (project) path += `&project=${encodeURIComponent(project)}`;
|
|
590
|
-
if (status) path += `&status=${encodeURIComponent(status)}`;
|
|
591
|
-
if (tags) path += `&tags=${encodeURIComponent(tags)}`;
|
|
592
|
-
return this.request("GET", path);
|
|
64
|
+
clear(): void {
|
|
65
|
+
this.logs = [];
|
|
593
66
|
}
|
|
594
67
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
tags?: string[],
|
|
601
|
-
): Promise<any> {
|
|
602
|
-
return this.request("POST", `/v1/decisions/${id}/supersede`, {
|
|
603
|
-
title,
|
|
604
|
-
rationale,
|
|
605
|
-
alternatives,
|
|
606
|
-
tags,
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
async checkDecisions(
|
|
611
|
-
query: string,
|
|
612
|
-
project?: string,
|
|
613
|
-
limit?: number,
|
|
614
|
-
threshold?: number,
|
|
615
|
-
includeSuperseded?: boolean,
|
|
616
|
-
): Promise<any> {
|
|
617
|
-
const params = new URLSearchParams();
|
|
618
|
-
params.set("query", query);
|
|
619
|
-
if (project) params.set("project", project);
|
|
620
|
-
if (limit !== undefined) params.set("limit", String(limit));
|
|
621
|
-
if (threshold !== undefined) params.set("threshold", String(threshold));
|
|
622
|
-
if (includeSuperseded) params.set("include_superseded", "true");
|
|
623
|
-
return this.request("GET", `/v1/decisions/check?${params.toString()}`);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// --------------------------------------------------------------------------
|
|
627
|
-
// Pattern operations
|
|
628
|
-
// --------------------------------------------------------------------------
|
|
629
|
-
|
|
630
|
-
async createPattern(
|
|
631
|
-
title: string,
|
|
632
|
-
description: string,
|
|
633
|
-
category?: string,
|
|
634
|
-
exampleCode?: string,
|
|
635
|
-
scope?: string,
|
|
636
|
-
tags?: string[],
|
|
637
|
-
sourceProject?: string,
|
|
638
|
-
): Promise<any> {
|
|
639
|
-
return this.request("POST", "/v1/patterns", {
|
|
640
|
-
title,
|
|
641
|
-
description,
|
|
642
|
-
category,
|
|
643
|
-
example_code: exampleCode,
|
|
644
|
-
scope,
|
|
645
|
-
tags,
|
|
646
|
-
source_project: sourceProject,
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
async searchPatterns(
|
|
651
|
-
query: string,
|
|
652
|
-
category?: string,
|
|
653
|
-
project?: string,
|
|
654
|
-
limit?: number,
|
|
655
|
-
threshold?: number,
|
|
656
|
-
): Promise<any> {
|
|
657
|
-
const params = new URLSearchParams();
|
|
658
|
-
params.set("query", query);
|
|
659
|
-
if (category) params.set("category", category);
|
|
660
|
-
if (project) params.set("project", project);
|
|
661
|
-
if (limit !== undefined) params.set("limit", String(limit));
|
|
662
|
-
if (threshold !== undefined) params.set("threshold", String(threshold));
|
|
663
|
-
return this.request("GET", `/v1/patterns/search?${params.toString()}`);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
async adoptPattern(id: string, project: string): Promise<any> {
|
|
667
|
-
return this.request("POST", `/v1/patterns/${id}/adopt`, { project });
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
async suggestPatterns(project: string, limit?: number): Promise<any> {
|
|
671
|
-
let path = `/v1/patterns/suggest?project=${encodeURIComponent(project)}`;
|
|
672
|
-
if (limit) path += `&limit=${limit}`;
|
|
673
|
-
return this.request("GET", path);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// --------------------------------------------------------------------------
|
|
677
|
-
// Project operations
|
|
678
|
-
// --------------------------------------------------------------------------
|
|
679
|
-
|
|
680
|
-
async registerProject(
|
|
681
|
-
slug: string,
|
|
682
|
-
name: string,
|
|
683
|
-
description?: string,
|
|
684
|
-
stack?: Record<string, unknown>,
|
|
685
|
-
repoUrl?: string,
|
|
686
|
-
): Promise<any> {
|
|
687
|
-
return this.request("POST", "/v1/projects", {
|
|
688
|
-
slug,
|
|
689
|
-
name,
|
|
690
|
-
description,
|
|
691
|
-
stack,
|
|
692
|
-
repo_url: repoUrl,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
async listProjects(limit: number = 20): Promise<any> {
|
|
697
|
-
return this.request("GET", `/v1/projects?limit=${limit}`);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
async getProject(slug: string): Promise<any> {
|
|
701
|
-
return this.request("GET", `/v1/projects/${encodeURIComponent(slug)}`);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
async addProjectRelationship(
|
|
705
|
-
from: string,
|
|
706
|
-
to: string,
|
|
707
|
-
type: string,
|
|
708
|
-
metadata?: Record<string, unknown>,
|
|
709
|
-
): Promise<any> {
|
|
710
|
-
return this.request("POST", `/v1/projects/${encodeURIComponent(from)}/relationships`, {
|
|
711
|
-
target_project: to,
|
|
712
|
-
relationship_type: type,
|
|
713
|
-
metadata,
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
async getProjectDependencies(project: string): Promise<any> {
|
|
718
|
-
return this.request(
|
|
719
|
-
"GET",
|
|
720
|
-
`/v1/projects/${encodeURIComponent(project)}/dependencies`,
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
async getProjectDependents(project: string): Promise<any> {
|
|
725
|
-
return this.request(
|
|
726
|
-
"GET",
|
|
727
|
-
`/v1/projects/${encodeURIComponent(project)}/dependents`,
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
async getProjectRelated(project: string): Promise<any> {
|
|
732
|
-
return this.request(
|
|
733
|
-
"GET",
|
|
734
|
-
`/v1/projects/${encodeURIComponent(project)}/related`,
|
|
735
|
-
);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
async projectImpact(project: string, changeDescription: string): Promise<any> {
|
|
739
|
-
return this.request(
|
|
740
|
-
"POST",
|
|
741
|
-
`/v1/projects/impact-analysis`,
|
|
742
|
-
{ project, change_description: changeDescription },
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
async getSharedPatterns(projectA: string, projectB: string): Promise<any> {
|
|
747
|
-
const params = new URLSearchParams();
|
|
748
|
-
params.set("a", projectA);
|
|
749
|
-
params.set("b", projectB);
|
|
750
|
-
return this.request(
|
|
751
|
-
"GET",
|
|
752
|
-
`/v1/projects/shared-patterns?${params.toString()}`,
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
async getProjectContext(project: string): Promise<any> {
|
|
757
|
-
return this.request(
|
|
758
|
-
"GET",
|
|
759
|
-
`/v1/projects/${encodeURIComponent(project)}/context`,
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// --------------------------------------------------------------------------
|
|
764
|
-
// Health & stats
|
|
765
|
-
// --------------------------------------------------------------------------
|
|
766
|
-
|
|
767
|
-
async health(): Promise<{ status: string }> {
|
|
768
|
-
return this.request<{ status: string }>("GET", "/v1/health");
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
async stats(): Promise<Stats> {
|
|
772
|
-
const response = await this.request<{ data: Stats }>(
|
|
773
|
-
"GET",
|
|
774
|
-
`/v1/stats?agent_id=${encodeURIComponent(this.agentId)}`,
|
|
775
|
-
);
|
|
68
|
+
getStats() {
|
|
69
|
+
const total = this.logs.length;
|
|
70
|
+
const successful = this.logs.filter(l => l.status === "success").length;
|
|
71
|
+
const failed = total - successful;
|
|
72
|
+
const avgDuration = total > 0 ? this.logs.reduce((sum, l) => sum + l.duration, 0) / total : 0;
|
|
776
73
|
return {
|
|
777
|
-
|
|
778
|
-
|
|
74
|
+
total,
|
|
75
|
+
successful,
|
|
76
|
+
failed,
|
|
77
|
+
successRate: total > 0 ? (successful / total) * 100 : 0,
|
|
78
|
+
avgDuration: Math.round(avgDuration),
|
|
779
79
|
};
|
|
780
80
|
}
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Export all memories as JSON
|
|
784
|
-
*/
|
|
785
|
-
async export(): Promise<Memory[]> {
|
|
786
|
-
const allMemories: Memory[] = [];
|
|
787
|
-
let offset = 0;
|
|
788
|
-
const limit = 100;
|
|
789
|
-
|
|
790
|
-
while (true) {
|
|
791
|
-
const batch = await this.list(limit, offset);
|
|
792
|
-
if (batch.length === 0) break;
|
|
793
|
-
allMemories.push(...batch);
|
|
794
|
-
offset += limit;
|
|
795
|
-
if (batch.length < limit) break;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
return allMemories;
|
|
799
|
-
}
|
|
800
81
|
}
|
|
801
82
|
|
|
802
83
|
// ============================================================================
|
|
803
|
-
//
|
|
84
|
+
// StatusReporter (Inlined)
|
|
804
85
|
// ============================================================================
|
|
805
86
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
87
|
+
class StatusReporter {
|
|
88
|
+
constructor(private debugLogger?: DebugLogger) {}
|
|
89
|
+
|
|
90
|
+
buildReport(connectionStatus: any, config: any, stats: any, toolGroups: any) {
|
|
91
|
+
const report = {
|
|
92
|
+
available: true,
|
|
93
|
+
connected: connectionStatus.connected,
|
|
94
|
+
apiVersion: connectionStatus.apiVersion,
|
|
95
|
+
config,
|
|
96
|
+
stats,
|
|
97
|
+
tools: toolGroups,
|
|
98
|
+
};
|
|
99
|
+
return report;
|
|
100
|
+
}
|
|
815
101
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
return false;
|
|
102
|
+
static formatReport(report: any): string {
|
|
103
|
+
return JSON.stringify(report, null, 2);
|
|
819
104
|
}
|
|
820
|
-
return CAPTURE_PATTERNS.some((pattern) => pattern.test(text));
|
|
821
105
|
}
|
|
822
106
|
|
|
823
107
|
// ============================================================================
|
|
824
|
-
// Plugin
|
|
108
|
+
// Plugin Code
|
|
825
109
|
// ============================================================================
|
|
826
110
|
|
|
827
|
-
|
|
828
|
-
const cfg = api.pluginConfig as MemoryRelayConfig | undefined;
|
|
111
|
+
const DEFAULT_API_URL = "https://api.memoryrelay.net";
|
|
829
112
|
|
|
830
|
-
|
|
113
|
+
export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
114
|
+
const cfg = api.pluginConfig as any;
|
|
115
|
+
|
|
831
116
|
const apiKey = cfg?.apiKey || process.env.MEMORYRELAY_API_KEY;
|
|
832
117
|
const agentId = cfg?.agentId || process.env.MEMORYRELAY_AGENT_ID || api.agentName;
|
|
833
118
|
|
|
834
119
|
if (!apiKey) {
|
|
835
|
-
api.logger.error(
|
|
836
|
-
"memory-memoryrelay: Missing API key in config or MEMORYRELAY_API_KEY env var.\n\n" +
|
|
837
|
-
"REQUIRED: Configure plugin via OpenClaw:\n\n" +
|
|
838
|
-
" openclaw config edit\n\n" +
|
|
839
|
-
'Navigate to plugins.entries.plugin-memoryrelay-ai.config and add:\n' +
|
|
840
|
-
' {\n' +
|
|
841
|
-
' "apiKey": "YOUR_API_KEY",\n' +
|
|
842
|
-
' "agentId": "YOUR_AGENT_ID"\n' +
|
|
843
|
-
' }\n\n' +
|
|
844
|
-
"Or set environment variable:\n" +
|
|
845
|
-
' export MEMORYRELAY_API_KEY="mem_prod_..."\n\n' +
|
|
846
|
-
"Then restart: openclaw gateway restart\n\n" +
|
|
847
|
-
"Get your API key from: https://memoryrelay.ai",
|
|
848
|
-
);
|
|
120
|
+
api.logger.error("memory-memoryrelay: Missing API key");
|
|
849
121
|
return;
|
|
850
122
|
}
|
|
851
123
|
|
|
852
124
|
if (!agentId) {
|
|
853
|
-
api.logger.error("memory-memoryrelay: Missing agentId
|
|
125
|
+
api.logger.error("memory-memoryrelay: Missing agentId");
|
|
854
126
|
return;
|
|
855
127
|
}
|
|
856
128
|
|
|
857
129
|
const apiUrl = cfg?.apiUrl || process.env.MEMORYRELAY_API_URL || DEFAULT_API_URL;
|
|
858
|
-
const defaultProject = cfg?.defaultProject || process.env.MEMORYRELAY_DEFAULT_PROJECT;
|
|
859
|
-
|
|
860
|
-
// ========================================================================
|
|
861
|
-
// Debug Logger and Status Reporter (v0.8.0)
|
|
862
|
-
// ========================================================================
|
|
863
130
|
|
|
864
131
|
const debugEnabled = cfg?.debug || false;
|
|
865
132
|
const verboseEnabled = cfg?.verbose || false;
|
|
866
133
|
const maxLogEntries = cfg?.maxLogEntries || 100;
|
|
867
134
|
|
|
868
|
-
// Note: logFile is deprecated in v0.9.3 (removed for OpenClaw security compliance)
|
|
869
|
-
// All debug logs are in-memory only. Use gateway methods to access logs.
|
|
870
|
-
|
|
871
135
|
let debugLogger: DebugLogger | undefined;
|
|
872
136
|
let statusReporter: StatusReporter | undefined;
|
|
873
137
|
|
|
@@ -877,2782 +141,26 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
877
141
|
verbose: verboseEnabled,
|
|
878
142
|
maxEntries: maxLogEntries,
|
|
879
143
|
});
|
|
880
|
-
|
|
881
|
-
api.logger.info(
|
|
882
|
-
`memory-memoryrelay: debug mode enabled (verbose: ${verboseEnabled}, maxEntries: ${maxLogEntries}, in-memory only)`
|
|
883
|
-
);
|
|
144
|
+
api.logger.info(`memory-memoryrelay: debug mode enabled`);
|
|
884
145
|
}
|
|
885
146
|
|
|
886
147
|
statusReporter = new StatusReporter(debugLogger);
|
|
887
|
-
|
|
888
|
-
const client = new MemoryRelayClient(apiKey, agentId, apiUrl, debugLogger, statusReporter, api);
|
|
889
|
-
|
|
890
|
-
// Verify connection on startup (with timeout)
|
|
891
|
-
try {
|
|
892
|
-
await client.health();
|
|
893
|
-
api.logger.info(`memory-memoryrelay: connected to ${apiUrl}`);
|
|
894
|
-
} catch (err) {
|
|
895
|
-
api.logger.error(`memory-memoryrelay: health check failed: ${String(err)}`);
|
|
896
|
-
// Continue loading plugin even if health check fails (will retry on first use)
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// ========================================================================
|
|
900
|
-
// Status Reporting (for openclaw status command)
|
|
901
|
-
// ========================================================================
|
|
902
148
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
endpoint: apiUrl,
|
|
916
|
-
lastCheck: new Date().toISOString(),
|
|
917
|
-
responseTime,
|
|
918
|
-
};
|
|
919
|
-
|
|
920
|
-
// Get memory stats
|
|
921
|
-
let memoryCount = 0;
|
|
922
|
-
try {
|
|
923
|
-
const stats = await client.stats();
|
|
924
|
-
memoryCount = stats.total_memories;
|
|
925
|
-
} catch (statsErr) {
|
|
926
|
-
api.logger.debug?.(`memory-memoryrelay: stats endpoint unavailable: ${String(statsErr)}`);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
const memoryStats = {
|
|
930
|
-
total_memories: memoryCount,
|
|
149
|
+
// Register a simple test tool
|
|
150
|
+
api.registerTool({
|
|
151
|
+
name: "memory_test",
|
|
152
|
+
description: "Test MemoryRelay connection",
|
|
153
|
+
handler: async () => {
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: "text",
|
|
158
|
+
text: `MemoryRelay plugin loaded! API: ${apiUrl}, Agent: ${agentId}`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
931
161
|
};
|
|
932
|
-
|
|
933
|
-
// Get config
|
|
934
|
-
const pluginConfig = {
|
|
935
|
-
agentId: agentId,
|
|
936
|
-
autoRecall: cfg?.autoRecall ?? true,
|
|
937
|
-
autoCapture: cfg?.autoCapture ?? false,
|
|
938
|
-
recallLimit: cfg?.recallLimit ?? 5,
|
|
939
|
-
recallThreshold: cfg?.recallThreshold ?? 0.3,
|
|
940
|
-
excludeChannels: cfg?.excludeChannels ?? [],
|
|
941
|
-
defaultProject: defaultProject,
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
// Build comprehensive status report
|
|
945
|
-
if (statusReporter) {
|
|
946
|
-
const report = statusReporter.buildReport(
|
|
947
|
-
connectionStatus,
|
|
948
|
-
pluginConfig,
|
|
949
|
-
memoryStats,
|
|
950
|
-
TOOL_GROUPS,
|
|
951
|
-
);
|
|
952
|
-
|
|
953
|
-
// Format and output
|
|
954
|
-
const formatted = StatusReporter.formatReport(report);
|
|
955
|
-
api.logger.info(formatted);
|
|
956
|
-
|
|
957
|
-
// Also return structured data for programmatic access
|
|
958
|
-
respond(true, {
|
|
959
|
-
available: true,
|
|
960
|
-
connected: isConnected,
|
|
961
|
-
endpoint: apiUrl,
|
|
962
|
-
memoryCount: memoryCount,
|
|
963
|
-
agentId: agentId,
|
|
964
|
-
debug: debugEnabled,
|
|
965
|
-
verbose: verboseEnabled,
|
|
966
|
-
report: report,
|
|
967
|
-
vector: {
|
|
968
|
-
available: true,
|
|
969
|
-
enabled: true,
|
|
970
|
-
},
|
|
971
|
-
});
|
|
972
|
-
} else {
|
|
973
|
-
// Fallback to simple status (shouldn't happen)
|
|
974
|
-
respond(true, {
|
|
975
|
-
available: true,
|
|
976
|
-
connected: isConnected,
|
|
977
|
-
endpoint: apiUrl,
|
|
978
|
-
memoryCount: memoryCount,
|
|
979
|
-
agentId: agentId,
|
|
980
|
-
vector: {
|
|
981
|
-
available: true,
|
|
982
|
-
enabled: true,
|
|
983
|
-
},
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
} catch (err) {
|
|
987
|
-
respond(true, {
|
|
988
|
-
available: false,
|
|
989
|
-
connected: false,
|
|
990
|
-
error: String(err),
|
|
991
|
-
endpoint: apiUrl,
|
|
992
|
-
agentId: agentId,
|
|
993
|
-
vector: {
|
|
994
|
-
available: false,
|
|
995
|
-
enabled: true,
|
|
996
|
-
},
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
// ========================================================================
|
|
1002
|
-
// Helper to check if a tool is enabled (by group)
|
|
1003
|
-
// ========================================================================
|
|
1004
|
-
|
|
1005
|
-
// Tool group mapping — matches MCP server's TOOL_GROUPS
|
|
1006
|
-
const TOOL_GROUPS: Record<string, string[]> = {
|
|
1007
|
-
memory: [
|
|
1008
|
-
"memory_store", "memory_recall", "memory_forget", "memory_list",
|
|
1009
|
-
"memory_get", "memory_update", "memory_batch_store", "memory_context",
|
|
1010
|
-
"memory_promote",
|
|
1011
|
-
],
|
|
1012
|
-
entity: ["entity_create", "entity_link", "entity_list", "entity_graph"],
|
|
1013
|
-
agent: ["agent_list", "agent_create", "agent_get"],
|
|
1014
|
-
session: ["session_start", "session_end", "session_recall", "session_list"],
|
|
1015
|
-
decision: ["decision_record", "decision_list", "decision_supersede", "decision_check"],
|
|
1016
|
-
pattern: ["pattern_create", "pattern_search", "pattern_adopt", "pattern_suggest"],
|
|
1017
|
-
project: [
|
|
1018
|
-
"project_register", "project_list", "project_info",
|
|
1019
|
-
"project_add_relationship", "project_dependencies", "project_dependents",
|
|
1020
|
-
"project_related", "project_impact", "project_shared_patterns", "project_context",
|
|
1021
|
-
],
|
|
1022
|
-
health: ["memory_health"],
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
// Build a set of enabled tool names from group names
|
|
1026
|
-
const enabledToolNames: Set<string> | null = (() => {
|
|
1027
|
-
if (!cfg?.enabledTools) return null; // all enabled
|
|
1028
|
-
const groups = cfg.enabledTools.split(",").map((s) => s.trim().toLowerCase());
|
|
1029
|
-
if (groups.includes("all")) return null;
|
|
1030
|
-
const enabled = new Set<string>();
|
|
1031
|
-
for (const group of groups) {
|
|
1032
|
-
const tools = TOOL_GROUPS[group];
|
|
1033
|
-
if (tools) {
|
|
1034
|
-
for (const tool of tools) {
|
|
1035
|
-
enabled.add(tool);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
return enabled;
|
|
1040
|
-
})();
|
|
1041
|
-
|
|
1042
|
-
function isToolEnabled(name: string): boolean {
|
|
1043
|
-
if (!enabledToolNames) return true;
|
|
1044
|
-
return enabledToolNames.has(name);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
// ========================================================================
|
|
1048
|
-
// Tools (39 total)
|
|
1049
|
-
// ========================================================================
|
|
1050
|
-
|
|
1051
|
-
// --------------------------------------------------------------------------
|
|
1052
|
-
// 1. memory_store
|
|
1053
|
-
// --------------------------------------------------------------------------
|
|
1054
|
-
if (isToolEnabled("memory_store")) {
|
|
1055
|
-
api.registerTool(
|
|
1056
|
-
{
|
|
1057
|
-
name: "memory_store",
|
|
1058
|
-
description:
|
|
1059
|
-
"Store a new memory in MemoryRelay. Use this to save important information, facts, preferences, or context that should be remembered for future conversations." +
|
|
1060
|
-
(defaultProject ? ` Project defaults to '${defaultProject}' if not specified.` : "") +
|
|
1061
|
-
" Set deduplicate=true to avoid storing near-duplicate memories.",
|
|
1062
|
-
parameters: {
|
|
1063
|
-
type: "object",
|
|
1064
|
-
properties: {
|
|
1065
|
-
content: {
|
|
1066
|
-
type: "string",
|
|
1067
|
-
description: "The memory content to store. Be specific and include relevant context.",
|
|
1068
|
-
},
|
|
1069
|
-
metadata: {
|
|
1070
|
-
type: "object",
|
|
1071
|
-
description: "Optional key-value metadata to attach to the memory",
|
|
1072
|
-
additionalProperties: { type: "string" },
|
|
1073
|
-
},
|
|
1074
|
-
deduplicate: {
|
|
1075
|
-
type: "boolean",
|
|
1076
|
-
description: "If true, check for duplicate memories before storing. Default false.",
|
|
1077
|
-
},
|
|
1078
|
-
dedup_threshold: {
|
|
1079
|
-
type: "number",
|
|
1080
|
-
description: "Similarity threshold for deduplication (0-1). Default 0.9.",
|
|
1081
|
-
},
|
|
1082
|
-
project: {
|
|
1083
|
-
type: "string",
|
|
1084
|
-
description: "Project slug to associate with this memory.",
|
|
1085
|
-
},
|
|
1086
|
-
importance: {
|
|
1087
|
-
type: "number",
|
|
1088
|
-
description: "Importance score (0-1). Higher values are retained longer.",
|
|
1089
|
-
},
|
|
1090
|
-
tier: {
|
|
1091
|
-
type: "string",
|
|
1092
|
-
description: "Memory tier: hot, warm, or cold.",
|
|
1093
|
-
enum: ["hot", "warm", "cold"],
|
|
1094
|
-
},
|
|
1095
|
-
},
|
|
1096
|
-
required: ["content"],
|
|
1097
|
-
},
|
|
1098
|
-
execute: async (
|
|
1099
|
-
_id,
|
|
1100
|
-
args: {
|
|
1101
|
-
content: string;
|
|
1102
|
-
metadata?: Record<string, string>;
|
|
1103
|
-
deduplicate?: boolean;
|
|
1104
|
-
dedup_threshold?: number;
|
|
1105
|
-
project?: string;
|
|
1106
|
-
importance?: number;
|
|
1107
|
-
tier?: string;
|
|
1108
|
-
},
|
|
1109
|
-
) => {
|
|
1110
|
-
try {
|
|
1111
|
-
const { content, metadata, ...opts } = args;
|
|
1112
|
-
if (!opts.project && defaultProject) opts.project = defaultProject;
|
|
1113
|
-
const memory = await client.store(content, metadata, opts);
|
|
1114
|
-
return {
|
|
1115
|
-
content: [
|
|
1116
|
-
{
|
|
1117
|
-
type: "text",
|
|
1118
|
-
text: `Memory stored successfully (id: ${memory.id.slice(0, 8)}...)`,
|
|
1119
|
-
},
|
|
1120
|
-
],
|
|
1121
|
-
details: { id: memory.id, stored: true },
|
|
1122
|
-
};
|
|
1123
|
-
} catch (err) {
|
|
1124
|
-
return {
|
|
1125
|
-
content: [{ type: "text", text: `Failed to store memory: ${String(err)}` }],
|
|
1126
|
-
details: { error: String(err) },
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
},
|
|
1130
|
-
},
|
|
1131
|
-
{ name: "memory_store" },
|
|
1132
|
-
);
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// --------------------------------------------------------------------------
|
|
1136
|
-
// 2. memory_recall
|
|
1137
|
-
// --------------------------------------------------------------------------
|
|
1138
|
-
if (isToolEnabled("memory_recall")) {
|
|
1139
|
-
api.registerTool(
|
|
1140
|
-
{
|
|
1141
|
-
name: "memory_recall",
|
|
1142
|
-
description:
|
|
1143
|
-
"Search memories using natural language. Returns the most relevant memories based on semantic similarity to the query." +
|
|
1144
|
-
(defaultProject ? ` Results scoped to project '${defaultProject}' by default; pass project explicitly to override or omit to search all.` : ""),
|
|
1145
|
-
parameters: {
|
|
1146
|
-
type: "object",
|
|
1147
|
-
properties: {
|
|
1148
|
-
query: {
|
|
1149
|
-
type: "string",
|
|
1150
|
-
description: "Natural language search query",
|
|
1151
|
-
},
|
|
1152
|
-
limit: {
|
|
1153
|
-
type: "number",
|
|
1154
|
-
description: "Maximum results (1-50). Default 5.",
|
|
1155
|
-
minimum: 1,
|
|
1156
|
-
maximum: 50,
|
|
1157
|
-
},
|
|
1158
|
-
threshold: {
|
|
1159
|
-
type: "number",
|
|
1160
|
-
description: "Minimum similarity threshold (0-1). Default 0.3.",
|
|
1161
|
-
},
|
|
1162
|
-
project: {
|
|
1163
|
-
type: "string",
|
|
1164
|
-
description: "Filter by project slug.",
|
|
1165
|
-
},
|
|
1166
|
-
tier: {
|
|
1167
|
-
type: "string",
|
|
1168
|
-
description: "Filter by memory tier: hot, warm, or cold.",
|
|
1169
|
-
enum: ["hot", "warm", "cold"],
|
|
1170
|
-
},
|
|
1171
|
-
min_importance: {
|
|
1172
|
-
type: "number",
|
|
1173
|
-
description: "Minimum importance score filter (0-1).",
|
|
1174
|
-
},
|
|
1175
|
-
compress: {
|
|
1176
|
-
type: "boolean",
|
|
1177
|
-
description: "If true, compress results for token efficiency.",
|
|
1178
|
-
},
|
|
1179
|
-
},
|
|
1180
|
-
required: ["query"],
|
|
1181
|
-
},
|
|
1182
|
-
execute: async (
|
|
1183
|
-
_id,
|
|
1184
|
-
args: {
|
|
1185
|
-
query: string;
|
|
1186
|
-
limit?: number;
|
|
1187
|
-
threshold?: number;
|
|
1188
|
-
project?: string;
|
|
1189
|
-
tier?: string;
|
|
1190
|
-
min_importance?: number;
|
|
1191
|
-
compress?: boolean;
|
|
1192
|
-
},
|
|
1193
|
-
) => {
|
|
1194
|
-
try {
|
|
1195
|
-
const {
|
|
1196
|
-
query,
|
|
1197
|
-
limit = 5,
|
|
1198
|
-
threshold,
|
|
1199
|
-
project,
|
|
1200
|
-
tier,
|
|
1201
|
-
min_importance,
|
|
1202
|
-
compress,
|
|
1203
|
-
} = args;
|
|
1204
|
-
const searchThreshold = threshold ?? cfg?.recallThreshold ?? 0.3;
|
|
1205
|
-
const searchProject = project ?? defaultProject;
|
|
1206
|
-
const results = await client.search(query, limit, searchThreshold, {
|
|
1207
|
-
project: searchProject,
|
|
1208
|
-
tier,
|
|
1209
|
-
min_importance,
|
|
1210
|
-
compress,
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
if (results.length === 0) {
|
|
1214
|
-
return {
|
|
1215
|
-
content: [{ type: "text", text: "No relevant memories found." }],
|
|
1216
|
-
details: { count: 0 },
|
|
1217
|
-
};
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
const formatted = results
|
|
1221
|
-
.map(
|
|
1222
|
-
(r) =>
|
|
1223
|
-
`- [${r.score.toFixed(2)}] ${r.memory.content.slice(0, 200)}${
|
|
1224
|
-
r.memory.content.length > 200 ? "..." : ""
|
|
1225
|
-
}`,
|
|
1226
|
-
)
|
|
1227
|
-
.join("\n");
|
|
1228
|
-
|
|
1229
|
-
return {
|
|
1230
|
-
content: [
|
|
1231
|
-
{
|
|
1232
|
-
type: "text",
|
|
1233
|
-
text: `Found ${results.length} relevant memories:\n${formatted}`,
|
|
1234
|
-
},
|
|
1235
|
-
],
|
|
1236
|
-
details: {
|
|
1237
|
-
count: results.length,
|
|
1238
|
-
memories: results.map((r) => ({
|
|
1239
|
-
id: r.memory.id,
|
|
1240
|
-
content: r.memory.content,
|
|
1241
|
-
score: r.score,
|
|
1242
|
-
})),
|
|
1243
|
-
},
|
|
1244
|
-
};
|
|
1245
|
-
} catch (err) {
|
|
1246
|
-
return {
|
|
1247
|
-
content: [{ type: "text", text: `Search failed: ${String(err)}` }],
|
|
1248
|
-
details: { error: String(err) },
|
|
1249
|
-
};
|
|
1250
|
-
}
|
|
1251
|
-
},
|
|
1252
|
-
},
|
|
1253
|
-
{ name: "memory_recall" },
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
// --------------------------------------------------------------------------
|
|
1258
|
-
// 3. memory_forget
|
|
1259
|
-
// --------------------------------------------------------------------------
|
|
1260
|
-
if (isToolEnabled("memory_forget")) {
|
|
1261
|
-
api.registerTool(
|
|
1262
|
-
{
|
|
1263
|
-
name: "memory_forget",
|
|
1264
|
-
description: "Delete a memory by ID, or search by query to find candidates. Provide memoryId for direct deletion, or query to search first. A single high-confidence match (>0.9) is auto-deleted; otherwise candidates are listed for you to choose.",
|
|
1265
|
-
parameters: {
|
|
1266
|
-
type: "object",
|
|
1267
|
-
properties: {
|
|
1268
|
-
memoryId: {
|
|
1269
|
-
type: "string",
|
|
1270
|
-
description: "Memory ID to delete",
|
|
1271
|
-
},
|
|
1272
|
-
query: {
|
|
1273
|
-
type: "string",
|
|
1274
|
-
description: "Search query to find memory",
|
|
1275
|
-
},
|
|
1276
|
-
},
|
|
1277
|
-
},
|
|
1278
|
-
execute: async (_id, { memoryId, query }: { memoryId?: string; query?: string }) => {
|
|
1279
|
-
if (memoryId) {
|
|
1280
|
-
try {
|
|
1281
|
-
await client.delete(memoryId);
|
|
1282
|
-
return {
|
|
1283
|
-
content: [{ type: "text", text: `Memory ${memoryId.slice(0, 8)}... deleted.` }],
|
|
1284
|
-
details: { action: "deleted", id: memoryId },
|
|
1285
|
-
};
|
|
1286
|
-
} catch (err) {
|
|
1287
|
-
return {
|
|
1288
|
-
content: [{ type: "text", text: `Delete failed: ${String(err)}` }],
|
|
1289
|
-
details: { error: String(err) },
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
if (query) {
|
|
1295
|
-
const results = await client.search(query, 5, 0.5, { project: defaultProject });
|
|
1296
|
-
|
|
1297
|
-
if (results.length === 0) {
|
|
1298
|
-
return {
|
|
1299
|
-
content: [{ type: "text", text: "No matching memories found." }],
|
|
1300
|
-
details: { count: 0 },
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// If single high-confidence match, delete it
|
|
1305
|
-
if (results.length === 1 && results[0].score > 0.9) {
|
|
1306
|
-
await client.delete(results[0].memory.id);
|
|
1307
|
-
return {
|
|
1308
|
-
content: [
|
|
1309
|
-
{ type: "text", text: `Forgotten: "${results[0].memory.content.slice(0, 60)}..."` },
|
|
1310
|
-
],
|
|
1311
|
-
details: { action: "deleted", id: results[0].memory.id },
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
const list = results
|
|
1316
|
-
.map((r) => `- [${r.memory.id.slice(0, 8)}] ${r.memory.content.slice(0, 60)}...`)
|
|
1317
|
-
.join("\n");
|
|
1318
|
-
|
|
1319
|
-
return {
|
|
1320
|
-
content: [
|
|
1321
|
-
{
|
|
1322
|
-
type: "text",
|
|
1323
|
-
text: `Found ${results.length} candidates. Specify memoryId:\n${list}`,
|
|
1324
|
-
},
|
|
1325
|
-
],
|
|
1326
|
-
details: { action: "candidates", count: results.length },
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
return {
|
|
1331
|
-
content: [{ type: "text", text: "Provide query or memoryId." }],
|
|
1332
|
-
details: { error: "missing_param" },
|
|
1333
|
-
};
|
|
1334
|
-
},
|
|
1335
|
-
},
|
|
1336
|
-
{ name: "memory_forget" },
|
|
1337
|
-
);
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
// --------------------------------------------------------------------------
|
|
1341
|
-
// 4. memory_list
|
|
1342
|
-
// --------------------------------------------------------------------------
|
|
1343
|
-
if (isToolEnabled("memory_list")) {
|
|
1344
|
-
api.registerTool(
|
|
1345
|
-
{
|
|
1346
|
-
name: "memory_list",
|
|
1347
|
-
description: "List recent memories chronologically for this agent. Use to review what has been stored or to find memory IDs for update/delete operations.",
|
|
1348
|
-
parameters: {
|
|
1349
|
-
type: "object",
|
|
1350
|
-
properties: {
|
|
1351
|
-
limit: {
|
|
1352
|
-
type: "number",
|
|
1353
|
-
description: "Number of memories to return (1-100). Default 20.",
|
|
1354
|
-
minimum: 1,
|
|
1355
|
-
maximum: 100,
|
|
1356
|
-
},
|
|
1357
|
-
offset: {
|
|
1358
|
-
type: "number",
|
|
1359
|
-
description: "Offset for pagination. Default 0.",
|
|
1360
|
-
minimum: 0,
|
|
1361
|
-
},
|
|
1362
|
-
},
|
|
1363
|
-
},
|
|
1364
|
-
execute: async (_id, args: { limit?: number; offset?: number }) => {
|
|
1365
|
-
try {
|
|
1366
|
-
const memories = await client.list(args.limit ?? 20, args.offset ?? 0);
|
|
1367
|
-
if (memories.length === 0) {
|
|
1368
|
-
return {
|
|
1369
|
-
content: [{ type: "text", text: "No memories found." }],
|
|
1370
|
-
details: { count: 0 },
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
const formatted = memories
|
|
1374
|
-
.map((m) => `- [${m.id.slice(0, 8)}] ${m.content.slice(0, 120)}`)
|
|
1375
|
-
.join("\n");
|
|
1376
|
-
return {
|
|
1377
|
-
content: [{ type: "text", text: `${memories.length} memories:\n${formatted}` }],
|
|
1378
|
-
details: { count: memories.length, memories },
|
|
1379
|
-
};
|
|
1380
|
-
} catch (err) {
|
|
1381
|
-
return {
|
|
1382
|
-
content: [{ type: "text", text: `Failed to list memories: ${String(err)}` }],
|
|
1383
|
-
details: { error: String(err) },
|
|
1384
|
-
};
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
|
-
},
|
|
1388
|
-
{ name: "memory_list" },
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
// --------------------------------------------------------------------------
|
|
1393
|
-
// 5. memory_get
|
|
1394
|
-
// --------------------------------------------------------------------------
|
|
1395
|
-
if (isToolEnabled("memory_get")) {
|
|
1396
|
-
api.registerTool(
|
|
1397
|
-
{
|
|
1398
|
-
name: "memory_get",
|
|
1399
|
-
description: "Retrieve a specific memory by its ID.",
|
|
1400
|
-
parameters: {
|
|
1401
|
-
type: "object",
|
|
1402
|
-
properties: {
|
|
1403
|
-
id: {
|
|
1404
|
-
type: "string",
|
|
1405
|
-
description: "The memory ID (UUID) to retrieve.",
|
|
1406
|
-
},
|
|
1407
|
-
},
|
|
1408
|
-
required: ["id"],
|
|
1409
|
-
},
|
|
1410
|
-
execute: async (_id, args: { id: string }) => {
|
|
1411
|
-
try {
|
|
1412
|
-
const memory = await client.get(args.id);
|
|
1413
|
-
return {
|
|
1414
|
-
content: [{ type: "text", text: JSON.stringify(memory, null, 2) }],
|
|
1415
|
-
details: { memory },
|
|
1416
|
-
};
|
|
1417
|
-
} catch (err) {
|
|
1418
|
-
return {
|
|
1419
|
-
content: [{ type: "text", text: `Failed to get memory: ${String(err)}` }],
|
|
1420
|
-
details: { error: String(err) },
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
},
|
|
1424
|
-
},
|
|
1425
|
-
{ name: "memory_get" },
|
|
1426
|
-
);
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
// --------------------------------------------------------------------------
|
|
1430
|
-
// 6. memory_update
|
|
1431
|
-
// --------------------------------------------------------------------------
|
|
1432
|
-
if (isToolEnabled("memory_update")) {
|
|
1433
|
-
api.registerTool(
|
|
1434
|
-
{
|
|
1435
|
-
name: "memory_update",
|
|
1436
|
-
description: "Update the content of an existing memory. Use to correct or expand stored information.",
|
|
1437
|
-
parameters: {
|
|
1438
|
-
type: "object",
|
|
1439
|
-
properties: {
|
|
1440
|
-
id: {
|
|
1441
|
-
type: "string",
|
|
1442
|
-
description: "The memory ID (UUID) to update.",
|
|
1443
|
-
},
|
|
1444
|
-
content: {
|
|
1445
|
-
type: "string",
|
|
1446
|
-
description: "The new content to replace the existing memory.",
|
|
1447
|
-
},
|
|
1448
|
-
metadata: {
|
|
1449
|
-
type: "object",
|
|
1450
|
-
description: "Updated metadata (replaces existing).",
|
|
1451
|
-
additionalProperties: { type: "string" },
|
|
1452
|
-
},
|
|
1453
|
-
},
|
|
1454
|
-
required: ["id", "content"],
|
|
1455
|
-
},
|
|
1456
|
-
execute: async (_id, args: { id: string; content: string; metadata?: Record<string, string> }) => {
|
|
1457
|
-
try {
|
|
1458
|
-
const memory = await client.update(args.id, args.content, args.metadata);
|
|
1459
|
-
return {
|
|
1460
|
-
content: [{ type: "text", text: `Memory ${args.id.slice(0, 8)}... updated.` }],
|
|
1461
|
-
details: { id: memory.id, updated: true },
|
|
1462
|
-
};
|
|
1463
|
-
} catch (err) {
|
|
1464
|
-
return {
|
|
1465
|
-
content: [{ type: "text", text: `Failed to update memory: ${String(err)}` }],
|
|
1466
|
-
details: { error: String(err) },
|
|
1467
|
-
};
|
|
1468
|
-
}
|
|
1469
|
-
},
|
|
1470
|
-
},
|
|
1471
|
-
{ name: "memory_update" },
|
|
1472
|
-
);
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// --------------------------------------------------------------------------
|
|
1476
|
-
// 7. memory_batch_store
|
|
1477
|
-
// --------------------------------------------------------------------------
|
|
1478
|
-
if (isToolEnabled("memory_batch_store")) {
|
|
1479
|
-
api.registerTool(
|
|
1480
|
-
{
|
|
1481
|
-
name: "memory_batch_store",
|
|
1482
|
-
description: "Store multiple memories at once. More efficient than individual calls for bulk storage.",
|
|
1483
|
-
parameters: {
|
|
1484
|
-
type: "object",
|
|
1485
|
-
properties: {
|
|
1486
|
-
memories: {
|
|
1487
|
-
type: "array",
|
|
1488
|
-
description: "Array of memories to store.",
|
|
1489
|
-
items: {
|
|
1490
|
-
type: "object",
|
|
1491
|
-
properties: {
|
|
1492
|
-
content: { type: "string", description: "Memory content." },
|
|
1493
|
-
metadata: {
|
|
1494
|
-
type: "object",
|
|
1495
|
-
description: "Optional metadata.",
|
|
1496
|
-
additionalProperties: { type: "string" },
|
|
1497
|
-
},
|
|
1498
|
-
},
|
|
1499
|
-
required: ["content"],
|
|
1500
|
-
},
|
|
1501
|
-
},
|
|
1502
|
-
},
|
|
1503
|
-
required: ["memories"],
|
|
1504
|
-
},
|
|
1505
|
-
execute: async (
|
|
1506
|
-
_id,
|
|
1507
|
-
args: { memories: Array<{ content: string; metadata?: Record<string, string> }> },
|
|
1508
|
-
) => {
|
|
1509
|
-
try {
|
|
1510
|
-
const result = await client.batchStore(args.memories);
|
|
1511
|
-
return {
|
|
1512
|
-
content: [
|
|
1513
|
-
{
|
|
1514
|
-
type: "text",
|
|
1515
|
-
text: `Batch stored ${args.memories.length} memories successfully.`,
|
|
1516
|
-
},
|
|
1517
|
-
],
|
|
1518
|
-
details: { count: args.memories.length, result },
|
|
1519
|
-
};
|
|
1520
|
-
} catch (err) {
|
|
1521
|
-
return {
|
|
1522
|
-
content: [{ type: "text", text: `Batch store failed: ${String(err)}` }],
|
|
1523
|
-
details: { error: String(err) },
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
1526
|
-
},
|
|
1527
|
-
},
|
|
1528
|
-
{ name: "memory_batch_store" },
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// --------------------------------------------------------------------------
|
|
1533
|
-
// 8. memory_context
|
|
1534
|
-
// --------------------------------------------------------------------------
|
|
1535
|
-
if (isToolEnabled("memory_context")) {
|
|
1536
|
-
api.registerTool(
|
|
1537
|
-
{
|
|
1538
|
-
name: "memory_context",
|
|
1539
|
-
description:
|
|
1540
|
-
"Build a context window from relevant memories, optimized for injecting into agent prompts with token budget awareness." +
|
|
1541
|
-
(defaultProject ? ` Project defaults to '${defaultProject}' if not specified.` : ""),
|
|
1542
|
-
parameters: {
|
|
1543
|
-
type: "object",
|
|
1544
|
-
properties: {
|
|
1545
|
-
query: {
|
|
1546
|
-
type: "string",
|
|
1547
|
-
description: "The query to build context around.",
|
|
1548
|
-
},
|
|
1549
|
-
limit: {
|
|
1550
|
-
type: "number",
|
|
1551
|
-
description: "Maximum number of memories to include.",
|
|
1552
|
-
},
|
|
1553
|
-
threshold: {
|
|
1554
|
-
type: "number",
|
|
1555
|
-
description: "Minimum similarity threshold (0-1).",
|
|
1556
|
-
},
|
|
1557
|
-
max_tokens: {
|
|
1558
|
-
type: "number",
|
|
1559
|
-
description: "Maximum token budget for the context.",
|
|
1560
|
-
},
|
|
1561
|
-
project: {
|
|
1562
|
-
type: "string",
|
|
1563
|
-
description: "Project slug to scope the context.",
|
|
1564
|
-
},
|
|
1565
|
-
},
|
|
1566
|
-
required: ["query"],
|
|
1567
|
-
},
|
|
1568
|
-
execute: async (
|
|
1569
|
-
_id,
|
|
1570
|
-
args: { query: string; limit?: number; threshold?: number; max_tokens?: number; project?: string },
|
|
1571
|
-
) => {
|
|
1572
|
-
try {
|
|
1573
|
-
const project = args.project ?? defaultProject;
|
|
1574
|
-
const result = await client.buildContext(
|
|
1575
|
-
args.query,
|
|
1576
|
-
args.limit,
|
|
1577
|
-
args.threshold,
|
|
1578
|
-
args.max_tokens,
|
|
1579
|
-
project,
|
|
1580
|
-
);
|
|
1581
|
-
return {
|
|
1582
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1583
|
-
details: { result },
|
|
1584
|
-
};
|
|
1585
|
-
} catch (err) {
|
|
1586
|
-
return {
|
|
1587
|
-
content: [{ type: "text", text: `Context build failed: ${String(err)}` }],
|
|
1588
|
-
details: { error: String(err) },
|
|
1589
|
-
};
|
|
1590
|
-
}
|
|
1591
|
-
},
|
|
1592
|
-
},
|
|
1593
|
-
{ name: "memory_context" },
|
|
1594
|
-
);
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
// --------------------------------------------------------------------------
|
|
1598
|
-
// 9. memory_promote
|
|
1599
|
-
// --------------------------------------------------------------------------
|
|
1600
|
-
if (isToolEnabled("memory_promote")) {
|
|
1601
|
-
api.registerTool(
|
|
1602
|
-
{
|
|
1603
|
-
name: "memory_promote",
|
|
1604
|
-
description:
|
|
1605
|
-
"Promote a memory by updating its importance score and/or tier. Use to ensure critical memories are retained longer.",
|
|
1606
|
-
parameters: {
|
|
1607
|
-
type: "object",
|
|
1608
|
-
properties: {
|
|
1609
|
-
memory_id: {
|
|
1610
|
-
type: "string",
|
|
1611
|
-
description: "The memory ID to promote.",
|
|
1612
|
-
},
|
|
1613
|
-
importance: {
|
|
1614
|
-
type: "number",
|
|
1615
|
-
description: "New importance score (0-1).",
|
|
1616
|
-
minimum: 0,
|
|
1617
|
-
maximum: 1,
|
|
1618
|
-
},
|
|
1619
|
-
tier: {
|
|
1620
|
-
type: "string",
|
|
1621
|
-
description: "Target tier: hot, warm, or cold.",
|
|
1622
|
-
enum: ["hot", "warm", "cold"],
|
|
1623
|
-
},
|
|
1624
|
-
},
|
|
1625
|
-
required: ["memory_id", "importance"],
|
|
1626
|
-
},
|
|
1627
|
-
execute: async (_id, args: { memory_id: string; importance: number; tier?: string }) => {
|
|
1628
|
-
try {
|
|
1629
|
-
const result = await client.promote(args.memory_id, args.importance, args.tier);
|
|
1630
|
-
return {
|
|
1631
|
-
content: [
|
|
1632
|
-
{
|
|
1633
|
-
type: "text",
|
|
1634
|
-
text: `Memory ${args.memory_id.slice(0, 8)}... promoted (importance: ${args.importance}${args.tier ? `, tier: ${args.tier}` : ""}).`,
|
|
1635
|
-
},
|
|
1636
|
-
],
|
|
1637
|
-
details: { result },
|
|
1638
|
-
};
|
|
1639
|
-
} catch (err) {
|
|
1640
|
-
return {
|
|
1641
|
-
content: [{ type: "text", text: `Promote failed: ${String(err)}` }],
|
|
1642
|
-
details: { error: String(err) },
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
},
|
|
1646
|
-
},
|
|
1647
|
-
{ name: "memory_promote" },
|
|
1648
|
-
);
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
// --------------------------------------------------------------------------
|
|
1652
|
-
// 10. entity_create
|
|
1653
|
-
// --------------------------------------------------------------------------
|
|
1654
|
-
if (isToolEnabled("entity_create")) {
|
|
1655
|
-
api.registerTool(
|
|
1656
|
-
{
|
|
1657
|
-
name: "entity_create",
|
|
1658
|
-
description:
|
|
1659
|
-
"Create a named entity (person, place, organization, project, concept) for the knowledge graph. Entities help organize and connect memories.",
|
|
1660
|
-
parameters: {
|
|
1661
|
-
type: "object",
|
|
1662
|
-
properties: {
|
|
1663
|
-
name: {
|
|
1664
|
-
type: "string",
|
|
1665
|
-
description: "Entity name (1-200 characters).",
|
|
1666
|
-
},
|
|
1667
|
-
type: {
|
|
1668
|
-
type: "string",
|
|
1669
|
-
description: "Entity type classification.",
|
|
1670
|
-
enum: ["person", "place", "organization", "project", "concept", "other"],
|
|
1671
|
-
},
|
|
1672
|
-
metadata: {
|
|
1673
|
-
type: "object",
|
|
1674
|
-
description: "Optional key-value metadata.",
|
|
1675
|
-
additionalProperties: { type: "string" },
|
|
1676
|
-
},
|
|
1677
|
-
},
|
|
1678
|
-
required: ["name", "type"],
|
|
1679
|
-
},
|
|
1680
|
-
execute: async (
|
|
1681
|
-
_id,
|
|
1682
|
-
args: { name: string; type: string; metadata?: Record<string, string> },
|
|
1683
|
-
) => {
|
|
1684
|
-
try {
|
|
1685
|
-
const result = await client.createEntity(args.name, args.type, args.metadata);
|
|
1686
|
-
return {
|
|
1687
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1688
|
-
details: { result },
|
|
1689
|
-
};
|
|
1690
|
-
} catch (err) {
|
|
1691
|
-
return {
|
|
1692
|
-
content: [{ type: "text", text: `Failed to create entity: ${String(err)}` }],
|
|
1693
|
-
details: { error: String(err) },
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
},
|
|
1697
|
-
},
|
|
1698
|
-
{ name: "entity_create" },
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
// --------------------------------------------------------------------------
|
|
1703
|
-
// 11. entity_link
|
|
1704
|
-
// --------------------------------------------------------------------------
|
|
1705
|
-
if (isToolEnabled("entity_link")) {
|
|
1706
|
-
api.registerTool(
|
|
1707
|
-
{
|
|
1708
|
-
name: "entity_link",
|
|
1709
|
-
description: "Link an entity to a memory to establish relationships in the knowledge graph.",
|
|
1710
|
-
parameters: {
|
|
1711
|
-
type: "object",
|
|
1712
|
-
properties: {
|
|
1713
|
-
entity_id: {
|
|
1714
|
-
type: "string",
|
|
1715
|
-
description: "Entity UUID.",
|
|
1716
|
-
},
|
|
1717
|
-
memory_id: {
|
|
1718
|
-
type: "string",
|
|
1719
|
-
description: "Memory UUID.",
|
|
1720
|
-
},
|
|
1721
|
-
relationship: {
|
|
1722
|
-
type: "string",
|
|
1723
|
-
description:
|
|
1724
|
-
'Relationship type (e.g., "mentioned_in", "created_by", "relates_to"). Default "mentioned_in".',
|
|
1725
|
-
},
|
|
1726
|
-
},
|
|
1727
|
-
required: ["entity_id", "memory_id"],
|
|
1728
|
-
},
|
|
1729
|
-
execute: async (
|
|
1730
|
-
_id,
|
|
1731
|
-
args: { entity_id: string; memory_id: string; relationship?: string },
|
|
1732
|
-
) => {
|
|
1733
|
-
try {
|
|
1734
|
-
const result = await client.linkEntity(
|
|
1735
|
-
args.entity_id,
|
|
1736
|
-
args.memory_id,
|
|
1737
|
-
args.relationship,
|
|
1738
|
-
);
|
|
1739
|
-
return {
|
|
1740
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1741
|
-
details: { result },
|
|
1742
|
-
};
|
|
1743
|
-
} catch (err) {
|
|
1744
|
-
return {
|
|
1745
|
-
content: [{ type: "text", text: `Failed to link entity: ${String(err)}` }],
|
|
1746
|
-
details: { error: String(err) },
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
},
|
|
1750
|
-
},
|
|
1751
|
-
{ name: "entity_link" },
|
|
1752
|
-
);
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
// --------------------------------------------------------------------------
|
|
1756
|
-
// 12. entity_list
|
|
1757
|
-
// --------------------------------------------------------------------------
|
|
1758
|
-
if (isToolEnabled("entity_list")) {
|
|
1759
|
-
api.registerTool(
|
|
1760
|
-
{
|
|
1761
|
-
name: "entity_list",
|
|
1762
|
-
description: "List entities in the knowledge graph.",
|
|
1763
|
-
parameters: {
|
|
1764
|
-
type: "object",
|
|
1765
|
-
properties: {
|
|
1766
|
-
limit: {
|
|
1767
|
-
type: "number",
|
|
1768
|
-
description: "Maximum entities to return. Default 20.",
|
|
1769
|
-
minimum: 1,
|
|
1770
|
-
maximum: 100,
|
|
1771
|
-
},
|
|
1772
|
-
offset: {
|
|
1773
|
-
type: "number",
|
|
1774
|
-
description: "Offset for pagination. Default 0.",
|
|
1775
|
-
minimum: 0,
|
|
1776
|
-
},
|
|
1777
|
-
},
|
|
1778
|
-
},
|
|
1779
|
-
execute: async (_id, args: { limit?: number; offset?: number }) => {
|
|
1780
|
-
try {
|
|
1781
|
-
const result = await client.listEntities(args.limit, args.offset);
|
|
1782
|
-
return {
|
|
1783
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1784
|
-
details: { result },
|
|
1785
|
-
};
|
|
1786
|
-
} catch (err) {
|
|
1787
|
-
return {
|
|
1788
|
-
content: [{ type: "text", text: `Failed to list entities: ${String(err)}` }],
|
|
1789
|
-
details: { error: String(err) },
|
|
1790
|
-
};
|
|
1791
|
-
}
|
|
1792
|
-
},
|
|
1793
|
-
},
|
|
1794
|
-
{ name: "entity_list" },
|
|
1795
|
-
);
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
// --------------------------------------------------------------------------
|
|
1799
|
-
// 13. entity_graph
|
|
1800
|
-
// --------------------------------------------------------------------------
|
|
1801
|
-
if (isToolEnabled("entity_graph")) {
|
|
1802
|
-
api.registerTool(
|
|
1803
|
-
{
|
|
1804
|
-
name: "entity_graph",
|
|
1805
|
-
description:
|
|
1806
|
-
"Explore the knowledge graph around an entity. Returns the entity and its neighborhood of connected entities and memories.",
|
|
1807
|
-
parameters: {
|
|
1808
|
-
type: "object",
|
|
1809
|
-
properties: {
|
|
1810
|
-
entity_id: {
|
|
1811
|
-
type: "string",
|
|
1812
|
-
description: "Entity UUID to explore from.",
|
|
1813
|
-
},
|
|
1814
|
-
depth: {
|
|
1815
|
-
type: "number",
|
|
1816
|
-
description: "How many hops to traverse. Default 2.",
|
|
1817
|
-
minimum: 1,
|
|
1818
|
-
maximum: 5,
|
|
1819
|
-
},
|
|
1820
|
-
max_neighbors: {
|
|
1821
|
-
type: "number",
|
|
1822
|
-
description: "Maximum neighbors per node. Default 10.",
|
|
1823
|
-
minimum: 1,
|
|
1824
|
-
maximum: 50,
|
|
1825
|
-
},
|
|
1826
|
-
},
|
|
1827
|
-
required: ["entity_id"],
|
|
1828
|
-
},
|
|
1829
|
-
execute: async (
|
|
1830
|
-
_id,
|
|
1831
|
-
args: { entity_id: string; depth?: number; max_neighbors?: number },
|
|
1832
|
-
) => {
|
|
1833
|
-
try {
|
|
1834
|
-
const result = await client.entityGraph(
|
|
1835
|
-
args.entity_id,
|
|
1836
|
-
args.depth,
|
|
1837
|
-
args.max_neighbors,
|
|
1838
|
-
);
|
|
1839
|
-
return {
|
|
1840
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1841
|
-
details: { result },
|
|
1842
|
-
};
|
|
1843
|
-
} catch (err) {
|
|
1844
|
-
return {
|
|
1845
|
-
content: [{ type: "text", text: `Failed to get entity graph: ${String(err)}` }],
|
|
1846
|
-
details: { error: String(err) },
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
|
-
},
|
|
1850
|
-
},
|
|
1851
|
-
{ name: "entity_graph" },
|
|
1852
|
-
);
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// --------------------------------------------------------------------------
|
|
1856
|
-
// 14. agent_list
|
|
1857
|
-
// --------------------------------------------------------------------------
|
|
1858
|
-
if (isToolEnabled("agent_list")) {
|
|
1859
|
-
api.registerTool(
|
|
1860
|
-
{
|
|
1861
|
-
name: "agent_list",
|
|
1862
|
-
description: "List available agents.",
|
|
1863
|
-
parameters: {
|
|
1864
|
-
type: "object",
|
|
1865
|
-
properties: {
|
|
1866
|
-
limit: {
|
|
1867
|
-
type: "number",
|
|
1868
|
-
description: "Maximum agents to return. Default 20.",
|
|
1869
|
-
minimum: 1,
|
|
1870
|
-
maximum: 100,
|
|
1871
|
-
},
|
|
1872
|
-
},
|
|
1873
|
-
},
|
|
1874
|
-
execute: async (_id, args: { limit?: number }) => {
|
|
1875
|
-
try {
|
|
1876
|
-
const result = await client.listAgents(args.limit);
|
|
1877
|
-
return {
|
|
1878
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1879
|
-
details: { result },
|
|
1880
|
-
};
|
|
1881
|
-
} catch (err) {
|
|
1882
|
-
return {
|
|
1883
|
-
content: [{ type: "text", text: `Failed to list agents: ${String(err)}` }],
|
|
1884
|
-
details: { error: String(err) },
|
|
1885
|
-
};
|
|
1886
|
-
}
|
|
1887
|
-
},
|
|
1888
|
-
},
|
|
1889
|
-
{ name: "agent_list" },
|
|
1890
|
-
);
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
// --------------------------------------------------------------------------
|
|
1894
|
-
// 15. agent_create
|
|
1895
|
-
// --------------------------------------------------------------------------
|
|
1896
|
-
if (isToolEnabled("agent_create")) {
|
|
1897
|
-
api.registerTool(
|
|
1898
|
-
{
|
|
1899
|
-
name: "agent_create",
|
|
1900
|
-
description: "Create a new agent. Agents serve as memory namespaces and isolation boundaries.",
|
|
1901
|
-
parameters: {
|
|
1902
|
-
type: "object",
|
|
1903
|
-
properties: {
|
|
1904
|
-
name: {
|
|
1905
|
-
type: "string",
|
|
1906
|
-
description: "Agent name.",
|
|
1907
|
-
},
|
|
1908
|
-
description: {
|
|
1909
|
-
type: "string",
|
|
1910
|
-
description: "Optional agent description.",
|
|
1911
|
-
},
|
|
1912
|
-
},
|
|
1913
|
-
required: ["name"],
|
|
1914
|
-
},
|
|
1915
|
-
execute: async (_id, args: { name: string; description?: string }) => {
|
|
1916
|
-
try {
|
|
1917
|
-
const result = await client.createAgent(args.name, args.description);
|
|
1918
|
-
return {
|
|
1919
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1920
|
-
details: { result },
|
|
1921
|
-
};
|
|
1922
|
-
} catch (err) {
|
|
1923
|
-
return {
|
|
1924
|
-
content: [{ type: "text", text: `Failed to create agent: ${String(err)}` }],
|
|
1925
|
-
details: { error: String(err) },
|
|
1926
|
-
};
|
|
1927
|
-
}
|
|
1928
|
-
},
|
|
1929
|
-
},
|
|
1930
|
-
{ name: "agent_create" },
|
|
1931
|
-
);
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
// --------------------------------------------------------------------------
|
|
1935
|
-
// 16. agent_get
|
|
1936
|
-
// --------------------------------------------------------------------------
|
|
1937
|
-
if (isToolEnabled("agent_get")) {
|
|
1938
|
-
api.registerTool(
|
|
1939
|
-
{
|
|
1940
|
-
name: "agent_get",
|
|
1941
|
-
description: "Get details about a specific agent by ID.",
|
|
1942
|
-
parameters: {
|
|
1943
|
-
type: "object",
|
|
1944
|
-
properties: {
|
|
1945
|
-
id: {
|
|
1946
|
-
type: "string",
|
|
1947
|
-
description: "Agent UUID.",
|
|
1948
|
-
},
|
|
1949
|
-
},
|
|
1950
|
-
required: ["id"],
|
|
1951
|
-
},
|
|
1952
|
-
execute: async (_id, args: { id: string }) => {
|
|
1953
|
-
try {
|
|
1954
|
-
const result = await client.getAgent(args.id);
|
|
1955
|
-
return {
|
|
1956
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1957
|
-
details: { result },
|
|
1958
|
-
};
|
|
1959
|
-
} catch (err) {
|
|
1960
|
-
return {
|
|
1961
|
-
content: [{ type: "text", text: `Failed to get agent: ${String(err)}` }],
|
|
1962
|
-
details: { error: String(err) },
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
},
|
|
1966
|
-
},
|
|
1967
|
-
{ name: "agent_get" },
|
|
1968
|
-
);
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
// --------------------------------------------------------------------------
|
|
1972
|
-
// 17. session_start
|
|
1973
|
-
// --------------------------------------------------------------------------
|
|
1974
|
-
if (isToolEnabled("session_start")) {
|
|
1975
|
-
api.registerTool(
|
|
1976
|
-
{
|
|
1977
|
-
name: "session_start",
|
|
1978
|
-
description:
|
|
1979
|
-
"Start a new work session. Sessions track the lifecycle of a task or conversation for later review. Call this early in your workflow and save the returned session ID for session_end later." +
|
|
1980
|
-
(defaultProject ? ` Project defaults to '${defaultProject}' if not specified.` : ""),
|
|
1981
|
-
parameters: {
|
|
1982
|
-
type: "object",
|
|
1983
|
-
properties: {
|
|
1984
|
-
title: {
|
|
1985
|
-
type: "string",
|
|
1986
|
-
description: "Session title describing the goal or task.",
|
|
1987
|
-
},
|
|
1988
|
-
project: {
|
|
1989
|
-
type: "string",
|
|
1990
|
-
description: "Project slug to associate this session with.",
|
|
1991
|
-
},
|
|
1992
|
-
metadata: {
|
|
1993
|
-
type: "object",
|
|
1994
|
-
description: "Optional key-value metadata.",
|
|
1995
|
-
additionalProperties: { type: "string" },
|
|
1996
|
-
},
|
|
1997
|
-
},
|
|
1998
|
-
},
|
|
1999
|
-
execute: async (
|
|
2000
|
-
_id,
|
|
2001
|
-
args: { title?: string; project?: string; metadata?: Record<string, string> },
|
|
2002
|
-
) => {
|
|
2003
|
-
try {
|
|
2004
|
-
const project = args.project ?? defaultProject;
|
|
2005
|
-
const result = await client.startSession(args.title, project, args.metadata);
|
|
2006
|
-
return {
|
|
2007
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2008
|
-
details: { result },
|
|
2009
|
-
};
|
|
2010
|
-
} catch (err) {
|
|
2011
|
-
return {
|
|
2012
|
-
content: [{ type: "text", text: `Failed to start session: ${String(err)}` }],
|
|
2013
|
-
details: { error: String(err) },
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
},
|
|
2017
|
-
},
|
|
2018
|
-
{ name: "session_start" },
|
|
2019
|
-
);
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
// --------------------------------------------------------------------------
|
|
2023
|
-
// 18. session_end
|
|
2024
|
-
// --------------------------------------------------------------------------
|
|
2025
|
-
if (isToolEnabled("session_end")) {
|
|
2026
|
-
api.registerTool(
|
|
2027
|
-
{
|
|
2028
|
-
name: "session_end",
|
|
2029
|
-
description: "End an active session with a summary of what was accomplished. Always include a meaningful summary — it serves as the historical record of the session.",
|
|
2030
|
-
parameters: {
|
|
2031
|
-
type: "object",
|
|
2032
|
-
properties: {
|
|
2033
|
-
id: {
|
|
2034
|
-
type: "string",
|
|
2035
|
-
description: "Session ID to end.",
|
|
2036
|
-
},
|
|
2037
|
-
summary: {
|
|
2038
|
-
type: "string",
|
|
2039
|
-
description: "Summary of what was accomplished during this session.",
|
|
2040
|
-
},
|
|
2041
|
-
},
|
|
2042
|
-
required: ["id"],
|
|
2043
|
-
},
|
|
2044
|
-
execute: async (_id, args: { id: string; summary?: string }) => {
|
|
2045
|
-
try {
|
|
2046
|
-
const result = await client.endSession(args.id, args.summary);
|
|
2047
|
-
return {
|
|
2048
|
-
content: [{ type: "text", text: `Session ${args.id.slice(0, 8)}... ended.` }],
|
|
2049
|
-
details: { result },
|
|
2050
|
-
};
|
|
2051
|
-
} catch (err) {
|
|
2052
|
-
return {
|
|
2053
|
-
content: [{ type: "text", text: `Failed to end session: ${String(err)}` }],
|
|
2054
|
-
details: { error: String(err) },
|
|
2055
|
-
};
|
|
2056
|
-
}
|
|
2057
|
-
},
|
|
2058
|
-
},
|
|
2059
|
-
{ name: "session_end" },
|
|
2060
|
-
);
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
// --------------------------------------------------------------------------
|
|
2064
|
-
// 19. session_recall
|
|
2065
|
-
// --------------------------------------------------------------------------
|
|
2066
|
-
if (isToolEnabled("session_recall")) {
|
|
2067
|
-
api.registerTool(
|
|
2068
|
-
{
|
|
2069
|
-
name: "session_recall",
|
|
2070
|
-
description: "Retrieve details of a specific session including its timeline and associated memories.",
|
|
2071
|
-
parameters: {
|
|
2072
|
-
type: "object",
|
|
2073
|
-
properties: {
|
|
2074
|
-
id: {
|
|
2075
|
-
type: "string",
|
|
2076
|
-
description: "Session ID to retrieve.",
|
|
2077
|
-
},
|
|
2078
|
-
},
|
|
2079
|
-
required: ["id"],
|
|
2080
|
-
},
|
|
2081
|
-
execute: async (_id, args: { id: string }) => {
|
|
2082
|
-
try {
|
|
2083
|
-
const result = await client.getSession(args.id);
|
|
2084
|
-
return {
|
|
2085
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2086
|
-
details: { result },
|
|
2087
|
-
};
|
|
2088
|
-
} catch (err) {
|
|
2089
|
-
return {
|
|
2090
|
-
content: [{ type: "text", text: `Failed to recall session: ${String(err)}` }],
|
|
2091
|
-
details: { error: String(err) },
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
},
|
|
2095
|
-
},
|
|
2096
|
-
{ name: "session_recall" },
|
|
2097
|
-
);
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
// --------------------------------------------------------------------------
|
|
2101
|
-
// 20. session_list
|
|
2102
|
-
// --------------------------------------------------------------------------
|
|
2103
|
-
if (isToolEnabled("session_list")) {
|
|
2104
|
-
api.registerTool(
|
|
2105
|
-
{
|
|
2106
|
-
name: "session_list",
|
|
2107
|
-
description: "List sessions, optionally filtered by project or status." +
|
|
2108
|
-
(defaultProject ? ` Scoped to project '${defaultProject}' by default.` : ""),
|
|
2109
|
-
parameters: {
|
|
2110
|
-
type: "object",
|
|
2111
|
-
properties: {
|
|
2112
|
-
limit: {
|
|
2113
|
-
type: "number",
|
|
2114
|
-
description: "Maximum sessions to return. Default 20.",
|
|
2115
|
-
minimum: 1,
|
|
2116
|
-
maximum: 100,
|
|
2117
|
-
},
|
|
2118
|
-
project: {
|
|
2119
|
-
type: "string",
|
|
2120
|
-
description: "Filter by project slug.",
|
|
2121
|
-
},
|
|
2122
|
-
status: {
|
|
2123
|
-
type: "string",
|
|
2124
|
-
description: "Filter by status (active, ended).",
|
|
2125
|
-
enum: ["active", "ended"],
|
|
2126
|
-
},
|
|
2127
|
-
},
|
|
2128
|
-
},
|
|
2129
|
-
execute: async (
|
|
2130
|
-
_id,
|
|
2131
|
-
args: { limit?: number; project?: string; status?: string },
|
|
2132
|
-
) => {
|
|
2133
|
-
try {
|
|
2134
|
-
const project = args.project ?? defaultProject;
|
|
2135
|
-
const result = await client.listSessions(args.limit, project, args.status);
|
|
2136
|
-
return {
|
|
2137
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2138
|
-
details: { result },
|
|
2139
|
-
};
|
|
2140
|
-
} catch (err) {
|
|
2141
|
-
return {
|
|
2142
|
-
content: [{ type: "text", text: `Failed to list sessions: ${String(err)}` }],
|
|
2143
|
-
details: { error: String(err) },
|
|
2144
|
-
};
|
|
2145
|
-
}
|
|
2146
|
-
},
|
|
2147
|
-
},
|
|
2148
|
-
{ name: "session_list" },
|
|
2149
|
-
);
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
// --------------------------------------------------------------------------
|
|
2153
|
-
// 21. decision_record
|
|
2154
|
-
// --------------------------------------------------------------------------
|
|
2155
|
-
if (isToolEnabled("decision_record")) {
|
|
2156
|
-
api.registerTool(
|
|
2157
|
-
{
|
|
2158
|
-
name: "decision_record",
|
|
2159
|
-
description:
|
|
2160
|
-
"Record an architectural or design decision. Captures the rationale and alternatives considered for future reference. Always check existing decisions with decision_check first to avoid contradictions." +
|
|
2161
|
-
(defaultProject ? ` Project defaults to '${defaultProject}' if not specified.` : ""),
|
|
2162
|
-
parameters: {
|
|
2163
|
-
type: "object",
|
|
2164
|
-
properties: {
|
|
2165
|
-
title: {
|
|
2166
|
-
type: "string",
|
|
2167
|
-
description: "Short title summarizing the decision.",
|
|
2168
|
-
},
|
|
2169
|
-
rationale: {
|
|
2170
|
-
type: "string",
|
|
2171
|
-
description: "Why this decision was made. Include context and reasoning.",
|
|
2172
|
-
},
|
|
2173
|
-
alternatives: {
|
|
2174
|
-
type: "string",
|
|
2175
|
-
description: "What alternatives were considered and why they were rejected.",
|
|
2176
|
-
},
|
|
2177
|
-
project: {
|
|
2178
|
-
type: "string",
|
|
2179
|
-
description: "Project slug this decision applies to.",
|
|
2180
|
-
},
|
|
2181
|
-
tags: {
|
|
2182
|
-
type: "array",
|
|
2183
|
-
description: "Tags for categorizing the decision.",
|
|
2184
|
-
items: { type: "string" },
|
|
2185
|
-
},
|
|
2186
|
-
status: {
|
|
2187
|
-
type: "string",
|
|
2188
|
-
description: "Decision status.",
|
|
2189
|
-
enum: ["active", "experimental"],
|
|
2190
|
-
},
|
|
2191
|
-
},
|
|
2192
|
-
required: ["title", "rationale"],
|
|
2193
|
-
},
|
|
2194
|
-
execute: async (
|
|
2195
|
-
_id,
|
|
2196
|
-
args: {
|
|
2197
|
-
title: string;
|
|
2198
|
-
rationale: string;
|
|
2199
|
-
alternatives?: string;
|
|
2200
|
-
project?: string;
|
|
2201
|
-
tags?: string[];
|
|
2202
|
-
status?: string;
|
|
2203
|
-
},
|
|
2204
|
-
) => {
|
|
2205
|
-
try {
|
|
2206
|
-
const project = args.project ?? defaultProject;
|
|
2207
|
-
const result = await client.recordDecision(
|
|
2208
|
-
args.title,
|
|
2209
|
-
args.rationale,
|
|
2210
|
-
args.alternatives,
|
|
2211
|
-
project,
|
|
2212
|
-
args.tags,
|
|
2213
|
-
args.status,
|
|
2214
|
-
);
|
|
2215
|
-
return {
|
|
2216
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2217
|
-
details: { result },
|
|
2218
|
-
};
|
|
2219
|
-
} catch (err) {
|
|
2220
|
-
return {
|
|
2221
|
-
content: [{ type: "text", text: `Failed to record decision: ${String(err)}` }],
|
|
2222
|
-
details: { error: String(err) },
|
|
2223
|
-
};
|
|
2224
|
-
}
|
|
2225
|
-
},
|
|
2226
|
-
},
|
|
2227
|
-
{ name: "decision_record" },
|
|
2228
|
-
);
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
// --------------------------------------------------------------------------
|
|
2232
|
-
// 22. decision_list
|
|
2233
|
-
// --------------------------------------------------------------------------
|
|
2234
|
-
if (isToolEnabled("decision_list")) {
|
|
2235
|
-
api.registerTool(
|
|
2236
|
-
{
|
|
2237
|
-
name: "decision_list",
|
|
2238
|
-
description: "List recorded decisions, optionally filtered by project, status, or tags." +
|
|
2239
|
-
(defaultProject ? ` Scoped to project '${defaultProject}' by default.` : ""),
|
|
2240
|
-
parameters: {
|
|
2241
|
-
type: "object",
|
|
2242
|
-
properties: {
|
|
2243
|
-
limit: {
|
|
2244
|
-
type: "number",
|
|
2245
|
-
description: "Maximum decisions to return. Default 20.",
|
|
2246
|
-
minimum: 1,
|
|
2247
|
-
maximum: 100,
|
|
2248
|
-
},
|
|
2249
|
-
project: {
|
|
2250
|
-
type: "string",
|
|
2251
|
-
description: "Filter by project slug.",
|
|
2252
|
-
},
|
|
2253
|
-
status: {
|
|
2254
|
-
type: "string",
|
|
2255
|
-
description: "Filter by status.",
|
|
2256
|
-
enum: ["active", "superseded", "reverted", "experimental"],
|
|
2257
|
-
},
|
|
2258
|
-
tags: {
|
|
2259
|
-
type: "string",
|
|
2260
|
-
description: "Comma-separated tags to filter by.",
|
|
2261
|
-
},
|
|
2262
|
-
},
|
|
2263
|
-
},
|
|
2264
|
-
execute: async (
|
|
2265
|
-
_id,
|
|
2266
|
-
args: { limit?: number; project?: string; status?: string; tags?: string },
|
|
2267
|
-
) => {
|
|
2268
|
-
try {
|
|
2269
|
-
const project = args.project ?? defaultProject;
|
|
2270
|
-
const result = await client.listDecisions(args.limit, project, args.status, args.tags);
|
|
2271
|
-
return {
|
|
2272
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2273
|
-
details: { result },
|
|
2274
|
-
};
|
|
2275
|
-
} catch (err) {
|
|
2276
|
-
return {
|
|
2277
|
-
content: [{ type: "text", text: `Failed to list decisions: ${String(err)}` }],
|
|
2278
|
-
details: { error: String(err) },
|
|
2279
|
-
};
|
|
2280
|
-
}
|
|
2281
|
-
},
|
|
2282
|
-
},
|
|
2283
|
-
{ name: "decision_list" },
|
|
2284
|
-
);
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
// --------------------------------------------------------------------------
|
|
2288
|
-
// 23. decision_supersede
|
|
2289
|
-
// --------------------------------------------------------------------------
|
|
2290
|
-
if (isToolEnabled("decision_supersede")) {
|
|
2291
|
-
api.registerTool(
|
|
2292
|
-
{
|
|
2293
|
-
name: "decision_supersede",
|
|
2294
|
-
description:
|
|
2295
|
-
"Supersede an existing decision with a new one. The old decision is marked as superseded and linked to the replacement.",
|
|
2296
|
-
parameters: {
|
|
2297
|
-
type: "object",
|
|
2298
|
-
properties: {
|
|
2299
|
-
id: {
|
|
2300
|
-
type: "string",
|
|
2301
|
-
description: "ID of the decision to supersede.",
|
|
2302
|
-
},
|
|
2303
|
-
title: {
|
|
2304
|
-
type: "string",
|
|
2305
|
-
description: "Title of the new replacement decision.",
|
|
2306
|
-
},
|
|
2307
|
-
rationale: {
|
|
2308
|
-
type: "string",
|
|
2309
|
-
description: "Why the previous decision is being replaced.",
|
|
2310
|
-
},
|
|
2311
|
-
alternatives: {
|
|
2312
|
-
type: "string",
|
|
2313
|
-
description: "Alternatives considered for the new decision.",
|
|
2314
|
-
},
|
|
2315
|
-
tags: {
|
|
2316
|
-
type: "array",
|
|
2317
|
-
description: "Tags for the new decision.",
|
|
2318
|
-
items: { type: "string" },
|
|
2319
|
-
},
|
|
2320
|
-
},
|
|
2321
|
-
required: ["id", "title", "rationale"],
|
|
2322
|
-
},
|
|
2323
|
-
execute: async (
|
|
2324
|
-
_id,
|
|
2325
|
-
args: {
|
|
2326
|
-
id: string;
|
|
2327
|
-
title: string;
|
|
2328
|
-
rationale: string;
|
|
2329
|
-
alternatives?: string;
|
|
2330
|
-
tags?: string[];
|
|
2331
|
-
},
|
|
2332
|
-
) => {
|
|
2333
|
-
try {
|
|
2334
|
-
const result = await client.supersedeDecision(
|
|
2335
|
-
args.id,
|
|
2336
|
-
args.title,
|
|
2337
|
-
args.rationale,
|
|
2338
|
-
args.alternatives,
|
|
2339
|
-
args.tags,
|
|
2340
|
-
);
|
|
2341
|
-
return {
|
|
2342
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2343
|
-
details: { result },
|
|
2344
|
-
};
|
|
2345
|
-
} catch (err) {
|
|
2346
|
-
return {
|
|
2347
|
-
content: [{ type: "text", text: `Failed to supersede decision: ${String(err)}` }],
|
|
2348
|
-
details: { error: String(err) },
|
|
2349
|
-
};
|
|
2350
|
-
}
|
|
2351
|
-
},
|
|
2352
|
-
},
|
|
2353
|
-
{ name: "decision_supersede" },
|
|
2354
|
-
);
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
// --------------------------------------------------------------------------
|
|
2358
|
-
// 24. decision_check
|
|
2359
|
-
// --------------------------------------------------------------------------
|
|
2360
|
-
if (isToolEnabled("decision_check")) {
|
|
2361
|
-
api.registerTool(
|
|
2362
|
-
{
|
|
2363
|
-
name: "decision_check",
|
|
2364
|
-
description:
|
|
2365
|
-
"Check if there are existing decisions relevant to a topic. ALWAYS call this before making architectural choices to avoid contradicting past decisions." +
|
|
2366
|
-
(defaultProject ? ` Scoped to project '${defaultProject}' by default.` : ""),
|
|
2367
|
-
parameters: {
|
|
2368
|
-
type: "object",
|
|
2369
|
-
properties: {
|
|
2370
|
-
query: {
|
|
2371
|
-
type: "string",
|
|
2372
|
-
description: "Natural language description of the topic or decision area.",
|
|
2373
|
-
},
|
|
2374
|
-
project: {
|
|
2375
|
-
type: "string",
|
|
2376
|
-
description: "Project slug to scope the search.",
|
|
2377
|
-
},
|
|
2378
|
-
limit: {
|
|
2379
|
-
type: "number",
|
|
2380
|
-
description: "Maximum results. Default 5.",
|
|
2381
|
-
},
|
|
2382
|
-
threshold: {
|
|
2383
|
-
type: "number",
|
|
2384
|
-
description: "Minimum similarity threshold (0-1). Default 0.3.",
|
|
2385
|
-
},
|
|
2386
|
-
include_superseded: {
|
|
2387
|
-
type: "boolean",
|
|
2388
|
-
description: "Include superseded decisions in results. Default false.",
|
|
2389
|
-
},
|
|
2390
|
-
},
|
|
2391
|
-
required: ["query"],
|
|
2392
|
-
},
|
|
2393
|
-
execute: async (
|
|
2394
|
-
_id,
|
|
2395
|
-
args: {
|
|
2396
|
-
query: string;
|
|
2397
|
-
project?: string;
|
|
2398
|
-
limit?: number;
|
|
2399
|
-
threshold?: number;
|
|
2400
|
-
include_superseded?: boolean;
|
|
2401
|
-
},
|
|
2402
|
-
) => {
|
|
2403
|
-
try {
|
|
2404
|
-
const project = args.project ?? defaultProject;
|
|
2405
|
-
const result = await client.checkDecisions(
|
|
2406
|
-
args.query,
|
|
2407
|
-
project,
|
|
2408
|
-
args.limit,
|
|
2409
|
-
args.threshold,
|
|
2410
|
-
args.include_superseded,
|
|
2411
|
-
);
|
|
2412
|
-
return {
|
|
2413
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2414
|
-
details: { result },
|
|
2415
|
-
};
|
|
2416
|
-
} catch (err) {
|
|
2417
|
-
return {
|
|
2418
|
-
content: [{ type: "text", text: `Failed to check decisions: ${String(err)}` }],
|
|
2419
|
-
details: { error: String(err) },
|
|
2420
|
-
};
|
|
2421
|
-
}
|
|
2422
|
-
},
|
|
2423
|
-
},
|
|
2424
|
-
{ name: "decision_check" },
|
|
2425
|
-
);
|
|
2426
|
-
}
|
|
2427
|
-
|
|
2428
|
-
// --------------------------------------------------------------------------
|
|
2429
|
-
// 25. pattern_create
|
|
2430
|
-
// --------------------------------------------------------------------------
|
|
2431
|
-
if (isToolEnabled("pattern_create")) {
|
|
2432
|
-
api.registerTool(
|
|
2433
|
-
{
|
|
2434
|
-
name: "pattern_create",
|
|
2435
|
-
description:
|
|
2436
|
-
"Create a reusable pattern (coding convention, architecture pattern, or best practice) that can be shared across projects. Include example_code for maximum usefulness." +
|
|
2437
|
-
(defaultProject ? ` Source project defaults to '${defaultProject}' if not specified.` : ""),
|
|
2438
|
-
parameters: {
|
|
2439
|
-
type: "object",
|
|
2440
|
-
properties: {
|
|
2441
|
-
title: {
|
|
2442
|
-
type: "string",
|
|
2443
|
-
description: "Pattern title.",
|
|
2444
|
-
},
|
|
2445
|
-
description: {
|
|
2446
|
-
type: "string",
|
|
2447
|
-
description: "Detailed description of the pattern, when to use it, and why.",
|
|
2448
|
-
},
|
|
2449
|
-
category: {
|
|
2450
|
-
type: "string",
|
|
2451
|
-
description: "Category (e.g., architecture, testing, error-handling, naming).",
|
|
2452
|
-
},
|
|
2453
|
-
example_code: {
|
|
2454
|
-
type: "string",
|
|
2455
|
-
description: "Example code demonstrating the pattern.",
|
|
2456
|
-
},
|
|
2457
|
-
scope: {
|
|
2458
|
-
type: "string",
|
|
2459
|
-
description: "Scope: global (visible to all projects) or project (visible to source project only).",
|
|
2460
|
-
enum: ["global", "project"],
|
|
2461
|
-
},
|
|
2462
|
-
tags: {
|
|
2463
|
-
type: "array",
|
|
2464
|
-
description: "Tags for categorization.",
|
|
2465
|
-
items: { type: "string" },
|
|
2466
|
-
},
|
|
2467
|
-
source_project: {
|
|
2468
|
-
type: "string",
|
|
2469
|
-
description: "Project slug where this pattern originated.",
|
|
2470
|
-
},
|
|
2471
|
-
},
|
|
2472
|
-
required: ["title", "description"],
|
|
2473
|
-
},
|
|
2474
|
-
execute: async (
|
|
2475
|
-
_id,
|
|
2476
|
-
args: {
|
|
2477
|
-
title: string;
|
|
2478
|
-
description: string;
|
|
2479
|
-
category?: string;
|
|
2480
|
-
example_code?: string;
|
|
2481
|
-
scope?: string;
|
|
2482
|
-
tags?: string[];
|
|
2483
|
-
source_project?: string;
|
|
2484
|
-
},
|
|
2485
|
-
) => {
|
|
2486
|
-
try {
|
|
2487
|
-
const sourceProject = args.source_project ?? defaultProject;
|
|
2488
|
-
const result = await client.createPattern(
|
|
2489
|
-
args.title,
|
|
2490
|
-
args.description,
|
|
2491
|
-
args.category,
|
|
2492
|
-
args.example_code,
|
|
2493
|
-
args.scope,
|
|
2494
|
-
args.tags,
|
|
2495
|
-
sourceProject,
|
|
2496
|
-
);
|
|
2497
|
-
return {
|
|
2498
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2499
|
-
details: { result },
|
|
2500
|
-
};
|
|
2501
|
-
} catch (err) {
|
|
2502
|
-
return {
|
|
2503
|
-
content: [{ type: "text", text: `Failed to create pattern: ${String(err)}` }],
|
|
2504
|
-
details: { error: String(err) },
|
|
2505
|
-
};
|
|
2506
|
-
}
|
|
2507
|
-
},
|
|
2508
|
-
},
|
|
2509
|
-
{ name: "pattern_create" },
|
|
2510
|
-
);
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
// --------------------------------------------------------------------------
|
|
2514
|
-
// 26. pattern_search
|
|
2515
|
-
// --------------------------------------------------------------------------
|
|
2516
|
-
if (isToolEnabled("pattern_search")) {
|
|
2517
|
-
api.registerTool(
|
|
2518
|
-
{
|
|
2519
|
-
name: "pattern_search",
|
|
2520
|
-
description: "Search for established patterns by natural language query. Call this before writing code to find and follow existing conventions." +
|
|
2521
|
-
(defaultProject ? ` Scoped to project '${defaultProject}' by default.` : ""),
|
|
2522
|
-
parameters: {
|
|
2523
|
-
type: "object",
|
|
2524
|
-
properties: {
|
|
2525
|
-
query: {
|
|
2526
|
-
type: "string",
|
|
2527
|
-
description: "Natural language search query.",
|
|
2528
|
-
},
|
|
2529
|
-
category: {
|
|
2530
|
-
type: "string",
|
|
2531
|
-
description: "Filter by category.",
|
|
2532
|
-
},
|
|
2533
|
-
project: {
|
|
2534
|
-
type: "string",
|
|
2535
|
-
description: "Filter by project slug.",
|
|
2536
|
-
},
|
|
2537
|
-
limit: {
|
|
2538
|
-
type: "number",
|
|
2539
|
-
description: "Maximum results. Default 10.",
|
|
2540
|
-
},
|
|
2541
|
-
threshold: {
|
|
2542
|
-
type: "number",
|
|
2543
|
-
description: "Minimum similarity threshold (0-1). Default 0.3.",
|
|
2544
|
-
},
|
|
2545
|
-
},
|
|
2546
|
-
required: ["query"],
|
|
2547
|
-
},
|
|
2548
|
-
execute: async (
|
|
2549
|
-
_id,
|
|
2550
|
-
args: {
|
|
2551
|
-
query: string;
|
|
2552
|
-
category?: string;
|
|
2553
|
-
project?: string;
|
|
2554
|
-
limit?: number;
|
|
2555
|
-
threshold?: number;
|
|
2556
|
-
},
|
|
2557
|
-
) => {
|
|
2558
|
-
try {
|
|
2559
|
-
const project = args.project ?? defaultProject;
|
|
2560
|
-
const result = await client.searchPatterns(
|
|
2561
|
-
args.query,
|
|
2562
|
-
args.category,
|
|
2563
|
-
project,
|
|
2564
|
-
args.limit,
|
|
2565
|
-
args.threshold,
|
|
2566
|
-
);
|
|
2567
|
-
return {
|
|
2568
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2569
|
-
details: { result },
|
|
2570
|
-
};
|
|
2571
|
-
} catch (err) {
|
|
2572
|
-
return {
|
|
2573
|
-
content: [{ type: "text", text: `Failed to search patterns: ${String(err)}` }],
|
|
2574
|
-
details: { error: String(err) },
|
|
2575
|
-
};
|
|
2576
|
-
}
|
|
2577
|
-
},
|
|
2578
|
-
},
|
|
2579
|
-
{ name: "pattern_search" },
|
|
2580
|
-
);
|
|
2581
|
-
}
|
|
2582
|
-
|
|
2583
|
-
// --------------------------------------------------------------------------
|
|
2584
|
-
// 27. pattern_adopt
|
|
2585
|
-
// --------------------------------------------------------------------------
|
|
2586
|
-
if (isToolEnabled("pattern_adopt")) {
|
|
2587
|
-
api.registerTool(
|
|
2588
|
-
{
|
|
2589
|
-
name: "pattern_adopt",
|
|
2590
|
-
description: "Adopt an existing pattern for use in a project. Creates a link between the pattern and the project.",
|
|
2591
|
-
parameters: {
|
|
2592
|
-
type: "object",
|
|
2593
|
-
properties: {
|
|
2594
|
-
id: {
|
|
2595
|
-
type: "string",
|
|
2596
|
-
description: "Pattern ID to adopt.",
|
|
2597
|
-
},
|
|
2598
|
-
project: {
|
|
2599
|
-
type: "string",
|
|
2600
|
-
description: "Project slug adopting the pattern.",
|
|
2601
|
-
},
|
|
2602
|
-
},
|
|
2603
|
-
required: ["id", "project"],
|
|
2604
|
-
},
|
|
2605
|
-
execute: async (_id, args: { id: string; project: string }) => {
|
|
2606
|
-
try {
|
|
2607
|
-
const result = await client.adoptPattern(args.id, args.project);
|
|
2608
|
-
return {
|
|
2609
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2610
|
-
details: { result },
|
|
2611
|
-
};
|
|
2612
|
-
} catch (err) {
|
|
2613
|
-
return {
|
|
2614
|
-
content: [{ type: "text", text: `Failed to adopt pattern: ${String(err)}` }],
|
|
2615
|
-
details: { error: String(err) },
|
|
2616
|
-
};
|
|
2617
|
-
}
|
|
2618
|
-
},
|
|
2619
|
-
},
|
|
2620
|
-
{ name: "pattern_adopt" },
|
|
2621
|
-
);
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
// --------------------------------------------------------------------------
|
|
2625
|
-
// 28. pattern_suggest
|
|
2626
|
-
// --------------------------------------------------------------------------
|
|
2627
|
-
if (isToolEnabled("pattern_suggest")) {
|
|
2628
|
-
api.registerTool(
|
|
2629
|
-
{
|
|
2630
|
-
name: "pattern_suggest",
|
|
2631
|
-
description:
|
|
2632
|
-
"Get pattern suggestions for a project based on its stack and existing patterns from related projects.",
|
|
2633
|
-
parameters: {
|
|
2634
|
-
type: "object",
|
|
2635
|
-
properties: {
|
|
2636
|
-
project: {
|
|
2637
|
-
type: "string",
|
|
2638
|
-
description: "Project slug to get suggestions for.",
|
|
2639
|
-
},
|
|
2640
|
-
limit: {
|
|
2641
|
-
type: "number",
|
|
2642
|
-
description: "Maximum suggestions. Default 10.",
|
|
2643
|
-
},
|
|
2644
|
-
},
|
|
2645
|
-
required: ["project"],
|
|
2646
|
-
},
|
|
2647
|
-
execute: async (_id, args: { project: string; limit?: number }) => {
|
|
2648
|
-
try {
|
|
2649
|
-
const result = await client.suggestPatterns(args.project, args.limit);
|
|
2650
|
-
return {
|
|
2651
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2652
|
-
details: { result },
|
|
2653
|
-
};
|
|
2654
|
-
} catch (err) {
|
|
2655
|
-
return {
|
|
2656
|
-
content: [{ type: "text", text: `Failed to suggest patterns: ${String(err)}` }],
|
|
2657
|
-
details: { error: String(err) },
|
|
2658
|
-
};
|
|
2659
|
-
}
|
|
2660
|
-
},
|
|
2661
|
-
},
|
|
2662
|
-
{ name: "pattern_suggest" },
|
|
2663
|
-
);
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
|
-
// --------------------------------------------------------------------------
|
|
2667
|
-
// 29. project_register
|
|
2668
|
-
// --------------------------------------------------------------------------
|
|
2669
|
-
if (isToolEnabled("project_register")) {
|
|
2670
|
-
api.registerTool(
|
|
2671
|
-
{
|
|
2672
|
-
name: "project_register",
|
|
2673
|
-
description: "Register a new project in MemoryRelay. Projects organize memories, decisions, patterns, and sessions.",
|
|
2674
|
-
parameters: {
|
|
2675
|
-
type: "object",
|
|
2676
|
-
properties: {
|
|
2677
|
-
slug: {
|
|
2678
|
-
type: "string",
|
|
2679
|
-
description: "URL-friendly project identifier (e.g., 'my-api', 'frontend-app').",
|
|
2680
|
-
},
|
|
2681
|
-
name: {
|
|
2682
|
-
type: "string",
|
|
2683
|
-
description: "Human-readable project name.",
|
|
2684
|
-
},
|
|
2685
|
-
description: {
|
|
2686
|
-
type: "string",
|
|
2687
|
-
description: "Project description.",
|
|
2688
|
-
},
|
|
2689
|
-
stack: {
|
|
2690
|
-
type: "object",
|
|
2691
|
-
description: "Technology stack details (e.g., {language: 'python', framework: 'fastapi'}).",
|
|
2692
|
-
},
|
|
2693
|
-
repo_url: {
|
|
2694
|
-
type: "string",
|
|
2695
|
-
description: "Repository URL.",
|
|
2696
|
-
},
|
|
2697
|
-
},
|
|
2698
|
-
required: ["slug", "name"],
|
|
2699
|
-
},
|
|
2700
|
-
execute: async (
|
|
2701
|
-
_id,
|
|
2702
|
-
args: {
|
|
2703
|
-
slug: string;
|
|
2704
|
-
name: string;
|
|
2705
|
-
description?: string;
|
|
2706
|
-
stack?: Record<string, unknown>;
|
|
2707
|
-
repo_url?: string;
|
|
2708
|
-
},
|
|
2709
|
-
) => {
|
|
2710
|
-
try {
|
|
2711
|
-
const result = await client.registerProject(
|
|
2712
|
-
args.slug,
|
|
2713
|
-
args.name,
|
|
2714
|
-
args.description,
|
|
2715
|
-
args.stack,
|
|
2716
|
-
args.repo_url,
|
|
2717
|
-
);
|
|
2718
|
-
return {
|
|
2719
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2720
|
-
details: { result },
|
|
2721
|
-
};
|
|
2722
|
-
} catch (err) {
|
|
2723
|
-
return {
|
|
2724
|
-
content: [{ type: "text", text: `Failed to register project: ${String(err)}` }],
|
|
2725
|
-
details: { error: String(err) },
|
|
2726
|
-
};
|
|
2727
|
-
}
|
|
2728
|
-
},
|
|
2729
|
-
},
|
|
2730
|
-
{ name: "project_register" },
|
|
2731
|
-
);
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2734
|
-
// --------------------------------------------------------------------------
|
|
2735
|
-
// 30. project_list
|
|
2736
|
-
// --------------------------------------------------------------------------
|
|
2737
|
-
if (isToolEnabled("project_list")) {
|
|
2738
|
-
api.registerTool(
|
|
2739
|
-
{
|
|
2740
|
-
name: "project_list",
|
|
2741
|
-
description: "List all registered projects.",
|
|
2742
|
-
parameters: {
|
|
2743
|
-
type: "object",
|
|
2744
|
-
properties: {
|
|
2745
|
-
limit: {
|
|
2746
|
-
type: "number",
|
|
2747
|
-
description: "Maximum projects to return. Default 20.",
|
|
2748
|
-
minimum: 1,
|
|
2749
|
-
maximum: 100,
|
|
2750
|
-
},
|
|
2751
|
-
},
|
|
2752
|
-
},
|
|
2753
|
-
execute: async (_id, args: { limit?: number }) => {
|
|
2754
|
-
try {
|
|
2755
|
-
const result = await client.listProjects(args.limit);
|
|
2756
|
-
return {
|
|
2757
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2758
|
-
details: { result },
|
|
2759
|
-
};
|
|
2760
|
-
} catch (err) {
|
|
2761
|
-
return {
|
|
2762
|
-
content: [{ type: "text", text: `Failed to list projects: ${String(err)}` }],
|
|
2763
|
-
details: { error: String(err) },
|
|
2764
|
-
};
|
|
2765
|
-
}
|
|
2766
|
-
},
|
|
2767
|
-
},
|
|
2768
|
-
{ name: "project_list" },
|
|
2769
|
-
);
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
// --------------------------------------------------------------------------
|
|
2773
|
-
// 31. project_info
|
|
2774
|
-
// --------------------------------------------------------------------------
|
|
2775
|
-
if (isToolEnabled("project_info")) {
|
|
2776
|
-
api.registerTool(
|
|
2777
|
-
{
|
|
2778
|
-
name: "project_info",
|
|
2779
|
-
description: "Get detailed information about a specific project.",
|
|
2780
|
-
parameters: {
|
|
2781
|
-
type: "object",
|
|
2782
|
-
properties: {
|
|
2783
|
-
slug: {
|
|
2784
|
-
type: "string",
|
|
2785
|
-
description: "Project slug.",
|
|
2786
|
-
},
|
|
2787
|
-
},
|
|
2788
|
-
required: ["slug"],
|
|
2789
|
-
},
|
|
2790
|
-
execute: async (_id, args: { slug: string }) => {
|
|
2791
|
-
try {
|
|
2792
|
-
const result = await client.getProject(args.slug);
|
|
2793
|
-
return {
|
|
2794
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2795
|
-
details: { result },
|
|
2796
|
-
};
|
|
2797
|
-
} catch (err) {
|
|
2798
|
-
return {
|
|
2799
|
-
content: [{ type: "text", text: `Failed to get project: ${String(err)}` }],
|
|
2800
|
-
details: { error: String(err) },
|
|
2801
|
-
};
|
|
2802
|
-
}
|
|
2803
|
-
},
|
|
2804
|
-
},
|
|
2805
|
-
{ name: "project_info" },
|
|
2806
|
-
);
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
// --------------------------------------------------------------------------
|
|
2810
|
-
// 32. project_add_relationship
|
|
2811
|
-
// --------------------------------------------------------------------------
|
|
2812
|
-
if (isToolEnabled("project_add_relationship")) {
|
|
2813
|
-
api.registerTool(
|
|
2814
|
-
{
|
|
2815
|
-
name: "project_add_relationship",
|
|
2816
|
-
description:
|
|
2817
|
-
"Add a relationship between two projects (e.g., depends_on, api_consumer, shares_schema, shares_infra, pattern_source, forked_from).",
|
|
2818
|
-
parameters: {
|
|
2819
|
-
type: "object",
|
|
2820
|
-
properties: {
|
|
2821
|
-
from: {
|
|
2822
|
-
type: "string",
|
|
2823
|
-
description: "Source project slug.",
|
|
2824
|
-
},
|
|
2825
|
-
to: {
|
|
2826
|
-
type: "string",
|
|
2827
|
-
description: "Target project slug.",
|
|
2828
|
-
},
|
|
2829
|
-
type: {
|
|
2830
|
-
type: "string",
|
|
2831
|
-
description: "Relationship type (e.g., depends_on, api_consumer, shares_schema, shares_infra, pattern_source, forked_from).",
|
|
2832
|
-
},
|
|
2833
|
-
metadata: {
|
|
2834
|
-
type: "object",
|
|
2835
|
-
description: "Optional metadata about the relationship.",
|
|
2836
|
-
},
|
|
2837
|
-
},
|
|
2838
|
-
required: ["from", "to", "type"],
|
|
2839
|
-
},
|
|
2840
|
-
execute: async (
|
|
2841
|
-
_id,
|
|
2842
|
-
args: { from: string; to: string; type: string; metadata?: Record<string, unknown> },
|
|
2843
|
-
) => {
|
|
2844
|
-
try {
|
|
2845
|
-
const result = await client.addProjectRelationship(
|
|
2846
|
-
args.from,
|
|
2847
|
-
args.to,
|
|
2848
|
-
args.type,
|
|
2849
|
-
args.metadata,
|
|
2850
|
-
);
|
|
2851
|
-
return {
|
|
2852
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2853
|
-
details: { result },
|
|
2854
|
-
};
|
|
2855
|
-
} catch (err) {
|
|
2856
|
-
return {
|
|
2857
|
-
content: [{ type: "text", text: `Failed to add relationship: ${String(err)}` }],
|
|
2858
|
-
details: { error: String(err) },
|
|
2859
|
-
};
|
|
2860
|
-
}
|
|
2861
|
-
},
|
|
2862
|
-
},
|
|
2863
|
-
{ name: "project_add_relationship" },
|
|
2864
|
-
);
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
// --------------------------------------------------------------------------
|
|
2868
|
-
// 33. project_dependencies
|
|
2869
|
-
// --------------------------------------------------------------------------
|
|
2870
|
-
if (isToolEnabled("project_dependencies")) {
|
|
2871
|
-
api.registerTool(
|
|
2872
|
-
{
|
|
2873
|
-
name: "project_dependencies",
|
|
2874
|
-
description: "List projects that a given project depends on.",
|
|
2875
|
-
parameters: {
|
|
2876
|
-
type: "object",
|
|
2877
|
-
properties: {
|
|
2878
|
-
project: {
|
|
2879
|
-
type: "string",
|
|
2880
|
-
description: "Project slug.",
|
|
2881
|
-
},
|
|
2882
|
-
},
|
|
2883
|
-
required: ["project"],
|
|
2884
|
-
},
|
|
2885
|
-
execute: async (_id, args: { project: string }) => {
|
|
2886
|
-
try {
|
|
2887
|
-
const result = await client.getProjectDependencies(args.project);
|
|
2888
|
-
return {
|
|
2889
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2890
|
-
details: { result },
|
|
2891
|
-
};
|
|
2892
|
-
} catch (err) {
|
|
2893
|
-
return {
|
|
2894
|
-
content: [{ type: "text", text: `Failed to get dependencies: ${String(err)}` }],
|
|
2895
|
-
details: { error: String(err) },
|
|
2896
|
-
};
|
|
2897
|
-
}
|
|
2898
|
-
},
|
|
2899
|
-
},
|
|
2900
|
-
{ name: "project_dependencies" },
|
|
2901
|
-
);
|
|
2902
|
-
}
|
|
2903
|
-
|
|
2904
|
-
// --------------------------------------------------------------------------
|
|
2905
|
-
// 34. project_dependents
|
|
2906
|
-
// --------------------------------------------------------------------------
|
|
2907
|
-
if (isToolEnabled("project_dependents")) {
|
|
2908
|
-
api.registerTool(
|
|
2909
|
-
{
|
|
2910
|
-
name: "project_dependents",
|
|
2911
|
-
description: "List projects that depend on a given project.",
|
|
2912
|
-
parameters: {
|
|
2913
|
-
type: "object",
|
|
2914
|
-
properties: {
|
|
2915
|
-
project: {
|
|
2916
|
-
type: "string",
|
|
2917
|
-
description: "Project slug.",
|
|
2918
|
-
},
|
|
2919
|
-
},
|
|
2920
|
-
required: ["project"],
|
|
2921
|
-
},
|
|
2922
|
-
execute: async (_id, args: { project: string }) => {
|
|
2923
|
-
try {
|
|
2924
|
-
const result = await client.getProjectDependents(args.project);
|
|
2925
|
-
return {
|
|
2926
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2927
|
-
details: { result },
|
|
2928
|
-
};
|
|
2929
|
-
} catch (err) {
|
|
2930
|
-
return {
|
|
2931
|
-
content: [{ type: "text", text: `Failed to get dependents: ${String(err)}` }],
|
|
2932
|
-
details: { error: String(err) },
|
|
2933
|
-
};
|
|
2934
|
-
}
|
|
2935
|
-
},
|
|
2936
|
-
},
|
|
2937
|
-
{ name: "project_dependents" },
|
|
2938
|
-
);
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
// --------------------------------------------------------------------------
|
|
2942
|
-
// 35. project_related
|
|
2943
|
-
// --------------------------------------------------------------------------
|
|
2944
|
-
if (isToolEnabled("project_related")) {
|
|
2945
|
-
api.registerTool(
|
|
2946
|
-
{
|
|
2947
|
-
name: "project_related",
|
|
2948
|
-
description: "List all projects related to a given project (any relationship direction).",
|
|
2949
|
-
parameters: {
|
|
2950
|
-
type: "object",
|
|
2951
|
-
properties: {
|
|
2952
|
-
project: {
|
|
2953
|
-
type: "string",
|
|
2954
|
-
description: "Project slug.",
|
|
2955
|
-
},
|
|
2956
|
-
},
|
|
2957
|
-
required: ["project"],
|
|
2958
|
-
},
|
|
2959
|
-
execute: async (_id, args: { project: string }) => {
|
|
2960
|
-
try {
|
|
2961
|
-
const result = await client.getProjectRelated(args.project);
|
|
2962
|
-
return {
|
|
2963
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2964
|
-
details: { result },
|
|
2965
|
-
};
|
|
2966
|
-
} catch (err) {
|
|
2967
|
-
return {
|
|
2968
|
-
content: [{ type: "text", text: `Failed to get related projects: ${String(err)}` }],
|
|
2969
|
-
details: { error: String(err) },
|
|
2970
|
-
};
|
|
2971
|
-
}
|
|
2972
|
-
},
|
|
2973
|
-
},
|
|
2974
|
-
{ name: "project_related" },
|
|
2975
|
-
);
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
// --------------------------------------------------------------------------
|
|
2979
|
-
// 36. project_impact
|
|
2980
|
-
// --------------------------------------------------------------------------
|
|
2981
|
-
if (isToolEnabled("project_impact")) {
|
|
2982
|
-
api.registerTool(
|
|
2983
|
-
{
|
|
2984
|
-
name: "project_impact",
|
|
2985
|
-
description:
|
|
2986
|
-
"Analyze the impact of a proposed change on a project and its dependents. Helps understand blast radius before making changes.",
|
|
2987
|
-
parameters: {
|
|
2988
|
-
type: "object",
|
|
2989
|
-
properties: {
|
|
2990
|
-
project: {
|
|
2991
|
-
type: "string",
|
|
2992
|
-
description: "Project slug to analyze.",
|
|
2993
|
-
},
|
|
2994
|
-
change_description: {
|
|
2995
|
-
type: "string",
|
|
2996
|
-
description: "Description of the proposed change.",
|
|
2997
|
-
},
|
|
2998
|
-
},
|
|
2999
|
-
required: ["project", "change_description"],
|
|
3000
|
-
},
|
|
3001
|
-
execute: async (_id, args: { project: string; change_description: string }) => {
|
|
3002
|
-
try {
|
|
3003
|
-
const result = await client.projectImpact(args.project, args.change_description);
|
|
3004
|
-
return {
|
|
3005
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
3006
|
-
details: { result },
|
|
3007
|
-
};
|
|
3008
|
-
} catch (err) {
|
|
3009
|
-
return {
|
|
3010
|
-
content: [{ type: "text", text: `Failed to analyze impact: ${String(err)}` }],
|
|
3011
|
-
details: { error: String(err) },
|
|
3012
|
-
};
|
|
3013
|
-
}
|
|
3014
|
-
},
|
|
3015
|
-
},
|
|
3016
|
-
{ name: "project_impact" },
|
|
3017
|
-
);
|
|
3018
|
-
}
|
|
3019
|
-
|
|
3020
|
-
// --------------------------------------------------------------------------
|
|
3021
|
-
// 37. project_shared_patterns
|
|
3022
|
-
// --------------------------------------------------------------------------
|
|
3023
|
-
if (isToolEnabled("project_shared_patterns")) {
|
|
3024
|
-
api.registerTool(
|
|
3025
|
-
{
|
|
3026
|
-
name: "project_shared_patterns",
|
|
3027
|
-
description: "Find patterns shared between two projects. Useful for maintaining consistency across related projects.",
|
|
3028
|
-
parameters: {
|
|
3029
|
-
type: "object",
|
|
3030
|
-
properties: {
|
|
3031
|
-
project_a: {
|
|
3032
|
-
type: "string",
|
|
3033
|
-
description: "First project slug.",
|
|
3034
|
-
},
|
|
3035
|
-
project_b: {
|
|
3036
|
-
type: "string",
|
|
3037
|
-
description: "Second project slug.",
|
|
3038
|
-
},
|
|
3039
|
-
},
|
|
3040
|
-
required: ["project_a", "project_b"],
|
|
3041
|
-
},
|
|
3042
|
-
execute: async (_id, args: { project_a: string; project_b: string }) => {
|
|
3043
|
-
try {
|
|
3044
|
-
const result = await client.getSharedPatterns(args.project_a, args.project_b);
|
|
3045
|
-
return {
|
|
3046
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
3047
|
-
details: { result },
|
|
3048
|
-
};
|
|
3049
|
-
} catch (err) {
|
|
3050
|
-
return {
|
|
3051
|
-
content: [{ type: "text", text: `Failed to get shared patterns: ${String(err)}` }],
|
|
3052
|
-
details: { error: String(err) },
|
|
3053
|
-
};
|
|
3054
|
-
}
|
|
3055
|
-
},
|
|
3056
|
-
},
|
|
3057
|
-
{ name: "project_shared_patterns" },
|
|
3058
|
-
);
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
// --------------------------------------------------------------------------
|
|
3062
|
-
// 38. project_context
|
|
3063
|
-
// --------------------------------------------------------------------------
|
|
3064
|
-
if (isToolEnabled("project_context")) {
|
|
3065
|
-
api.registerTool(
|
|
3066
|
-
{
|
|
3067
|
-
name: "project_context",
|
|
3068
|
-
description:
|
|
3069
|
-
"Load full project context including hot-tier memories, active decisions, adopted patterns, and recent sessions. Call this FIRST when starting work on a project to understand existing context before making changes.",
|
|
3070
|
-
parameters: {
|
|
3071
|
-
type: "object",
|
|
3072
|
-
properties: {
|
|
3073
|
-
project: {
|
|
3074
|
-
type: "string",
|
|
3075
|
-
description: "Project slug.",
|
|
3076
|
-
},
|
|
3077
|
-
},
|
|
3078
|
-
required: ["project"],
|
|
3079
|
-
},
|
|
3080
|
-
execute: async (_id, args: { project: string }) => {
|
|
3081
|
-
try {
|
|
3082
|
-
const result = await client.getProjectContext(args.project);
|
|
3083
|
-
return {
|
|
3084
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
3085
|
-
details: { result },
|
|
3086
|
-
};
|
|
3087
|
-
} catch (err) {
|
|
3088
|
-
return {
|
|
3089
|
-
content: [{ type: "text", text: `Failed to load project context: ${String(err)}` }],
|
|
3090
|
-
details: { error: String(err) },
|
|
3091
|
-
};
|
|
3092
|
-
}
|
|
3093
|
-
},
|
|
3094
|
-
},
|
|
3095
|
-
{ name: "project_context" },
|
|
3096
|
-
);
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
// --------------------------------------------------------------------------
|
|
3100
|
-
// 39. memory_health
|
|
3101
|
-
// --------------------------------------------------------------------------
|
|
3102
|
-
if (isToolEnabled("memory_health")) {
|
|
3103
|
-
api.registerTool(
|
|
3104
|
-
{
|
|
3105
|
-
name: "memory_health",
|
|
3106
|
-
description: "Check the MemoryRelay API connectivity and health status.",
|
|
3107
|
-
parameters: {
|
|
3108
|
-
type: "object",
|
|
3109
|
-
properties: {},
|
|
3110
|
-
},
|
|
3111
|
-
execute: async () => {
|
|
3112
|
-
try {
|
|
3113
|
-
const health = await client.health();
|
|
3114
|
-
return {
|
|
3115
|
-
content: [{ type: "text", text: JSON.stringify(health, null, 2) }],
|
|
3116
|
-
details: { health },
|
|
3117
|
-
};
|
|
3118
|
-
} catch (err) {
|
|
3119
|
-
return {
|
|
3120
|
-
content: [{ type: "text", text: `Health check failed: ${String(err)}` }],
|
|
3121
|
-
details: { error: String(err) },
|
|
3122
|
-
};
|
|
3123
|
-
}
|
|
3124
|
-
},
|
|
3125
|
-
},
|
|
3126
|
-
{ name: "memory_health" },
|
|
3127
|
-
);
|
|
3128
|
-
}
|
|
3129
|
-
|
|
3130
|
-
// ========================================================================
|
|
3131
|
-
// CLI Commands
|
|
3132
|
-
// ========================================================================
|
|
3133
|
-
|
|
3134
|
-
api.registerCli(
|
|
3135
|
-
({ program }) => {
|
|
3136
|
-
const mem = program.command("memoryrelay").description("MemoryRelay memory plugin commands");
|
|
3137
|
-
|
|
3138
|
-
mem
|
|
3139
|
-
.command("status")
|
|
3140
|
-
.description("Check MemoryRelay connection status")
|
|
3141
|
-
.action(async () => {
|
|
3142
|
-
try {
|
|
3143
|
-
const health = await client.health();
|
|
3144
|
-
const stats = await client.stats();
|
|
3145
|
-
console.log(`Status: ${health.status}`);
|
|
3146
|
-
console.log(`Agent ID: ${agentId}`);
|
|
3147
|
-
console.log(`API: ${apiUrl}`);
|
|
3148
|
-
console.log(`Total Memories: ${stats.total_memories}`);
|
|
3149
|
-
if (stats.last_updated) {
|
|
3150
|
-
console.log(`Last Updated: ${new Date(stats.last_updated).toLocaleString()}`);
|
|
3151
|
-
}
|
|
3152
|
-
} catch (err) {
|
|
3153
|
-
console.error(`Connection failed: ${String(err)}`);
|
|
3154
|
-
}
|
|
3155
|
-
});
|
|
3156
|
-
|
|
3157
|
-
mem
|
|
3158
|
-
.command("stats")
|
|
3159
|
-
.description("Show agent statistics")
|
|
3160
|
-
.action(async () => {
|
|
3161
|
-
try {
|
|
3162
|
-
const stats = await client.stats();
|
|
3163
|
-
console.log(`Total Memories: ${stats.total_memories}`);
|
|
3164
|
-
if (stats.last_updated) {
|
|
3165
|
-
console.log(`Last Updated: ${new Date(stats.last_updated).toLocaleString()}`);
|
|
3166
|
-
}
|
|
3167
|
-
} catch (err) {
|
|
3168
|
-
console.error(`Failed to fetch stats: ${String(err)}`);
|
|
3169
|
-
}
|
|
3170
|
-
});
|
|
3171
|
-
|
|
3172
|
-
mem
|
|
3173
|
-
.command("list")
|
|
3174
|
-
.description("List recent memories")
|
|
3175
|
-
.option("--limit <n>", "Max results", "10")
|
|
3176
|
-
.action(async (opts) => {
|
|
3177
|
-
try {
|
|
3178
|
-
const memories = await client.list(parseInt(opts.limit));
|
|
3179
|
-
for (const m of memories) {
|
|
3180
|
-
console.log(`[${m.id.slice(0, 8)}] ${m.content.slice(0, 80)}...`);
|
|
3181
|
-
}
|
|
3182
|
-
console.log(`\nTotal: ${memories.length} memories`);
|
|
3183
|
-
} catch (err) {
|
|
3184
|
-
console.error(`Failed to list memories: ${String(err)}`);
|
|
3185
|
-
}
|
|
3186
|
-
});
|
|
3187
|
-
|
|
3188
|
-
mem
|
|
3189
|
-
.command("search")
|
|
3190
|
-
.description("Search memories")
|
|
3191
|
-
.argument("<query>", "Search query")
|
|
3192
|
-
.option("--limit <n>", "Max results", "5")
|
|
3193
|
-
.action(async (query, opts) => {
|
|
3194
|
-
try {
|
|
3195
|
-
const results = await client.search(query, parseInt(opts.limit));
|
|
3196
|
-
for (const r of results) {
|
|
3197
|
-
console.log(`[${r.score.toFixed(2)}] ${r.memory.content.slice(0, 80)}...`);
|
|
3198
|
-
}
|
|
3199
|
-
} catch (err) {
|
|
3200
|
-
console.error(`Search failed: ${String(err)}`);
|
|
3201
|
-
}
|
|
3202
|
-
});
|
|
3203
|
-
|
|
3204
|
-
mem
|
|
3205
|
-
.command("delete")
|
|
3206
|
-
.description("Delete a memory by ID")
|
|
3207
|
-
.argument("<id>", "Memory ID")
|
|
3208
|
-
.action(async (id) => {
|
|
3209
|
-
try {
|
|
3210
|
-
await client.delete(id);
|
|
3211
|
-
console.log(`Memory ${id.slice(0, 8)}... deleted.`);
|
|
3212
|
-
} catch (err) {
|
|
3213
|
-
console.error(`Delete failed: ${String(err)}`);
|
|
3214
|
-
}
|
|
3215
|
-
});
|
|
3216
|
-
|
|
3217
|
-
mem
|
|
3218
|
-
.command("export")
|
|
3219
|
-
.description("Export all memories to JSON (outputs to stdout)")
|
|
3220
|
-
.action(async () => {
|
|
3221
|
-
try {
|
|
3222
|
-
const memories = await client.export();
|
|
3223
|
-
console.log(JSON.stringify(memories, null, 2));
|
|
3224
|
-
console.error(`\n# Exported ${memories.length} memories. Redirect stdout to save: memoryrelay export > memories.json`);
|
|
3225
|
-
} catch (err) {
|
|
3226
|
-
console.error(`Export failed: ${String(err)}`);
|
|
3227
|
-
}
|
|
3228
|
-
});
|
|
3229
162
|
},
|
|
3230
|
-
{ commands: ["memoryrelay"] },
|
|
3231
|
-
);
|
|
3232
|
-
|
|
3233
|
-
// ========================================================================
|
|
3234
|
-
// Lifecycle Hooks
|
|
3235
|
-
// ========================================================================
|
|
3236
|
-
|
|
3237
|
-
// Workflow instructions + auto-recall: always inject workflow guidance,
|
|
3238
|
-
// optionally recall relevant memories if autoRecall is enabled
|
|
3239
|
-
api.on("before_agent_start", async (event) => {
|
|
3240
|
-
if (!event.prompt || event.prompt.length < 10) {
|
|
3241
|
-
return;
|
|
3242
|
-
}
|
|
3243
|
-
|
|
3244
|
-
// Check if current channel is excluded
|
|
3245
|
-
if (cfg?.excludeChannels && event.channel) {
|
|
3246
|
-
const channelId = String(event.channel);
|
|
3247
|
-
if (cfg.excludeChannels.some((excluded) => channelId.includes(excluded))) {
|
|
3248
|
-
api.logger.debug?.(
|
|
3249
|
-
`memory-memoryrelay: skipping for excluded channel: ${channelId}`,
|
|
3250
|
-
);
|
|
3251
|
-
return;
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
|
|
3255
|
-
// Build workflow instructions dynamically based on enabled tools
|
|
3256
|
-
const lines: string[] = [
|
|
3257
|
-
"You have MemoryRelay tools available for persistent memory across sessions.",
|
|
3258
|
-
];
|
|
3259
|
-
|
|
3260
|
-
if (defaultProject) {
|
|
3261
|
-
lines.push(`Default project: \`${defaultProject}\` (auto-applied when you omit the project parameter).`);
|
|
3262
|
-
}
|
|
3263
|
-
|
|
3264
|
-
lines.push("", "## Recommended Workflow", "");
|
|
3265
|
-
|
|
3266
|
-
// Starting work section — only include steps for enabled tools
|
|
3267
|
-
const startSteps: string[] = [];
|
|
3268
|
-
if (isToolEnabled("project_context")) {
|
|
3269
|
-
startSteps.push(`**Load context**: Call \`project_context(${defaultProject ? `"${defaultProject}"` : "project"})\` to load hot-tier memories, active decisions, and adopted patterns`);
|
|
3270
|
-
}
|
|
3271
|
-
if (isToolEnabled("session_start")) {
|
|
3272
|
-
startSteps.push(`**Start session**: Call \`session_start(title${defaultProject ? "" : ", project"})\` to begin tracking your work`);
|
|
3273
|
-
}
|
|
3274
|
-
if (isToolEnabled("decision_check")) {
|
|
3275
|
-
startSteps.push(`**Check decisions**: Call \`decision_check(query${defaultProject ? "" : ", project"})\` before making architectural choices`);
|
|
3276
|
-
}
|
|
3277
|
-
if (isToolEnabled("pattern_search")) {
|
|
3278
|
-
startSteps.push("**Find patterns**: Call `pattern_search(query)` to find established conventions before writing code");
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
|
-
if (startSteps.length > 0) {
|
|
3282
|
-
lines.push("When starting work on a project:");
|
|
3283
|
-
startSteps.forEach((step, i) => lines.push(`${i + 1}. ${step}`));
|
|
3284
|
-
lines.push("");
|
|
3285
|
-
}
|
|
3286
|
-
|
|
3287
|
-
// While working section
|
|
3288
|
-
const workSteps: string[] = [];
|
|
3289
|
-
if (isToolEnabled("memory_store")) {
|
|
3290
|
-
workSteps.push("**Store findings**: Call `memory_store(content, metadata)` for important information worth remembering");
|
|
3291
|
-
}
|
|
3292
|
-
if (isToolEnabled("decision_record")) {
|
|
3293
|
-
workSteps.push(`**Record decisions**: Call \`decision_record(title, rationale${defaultProject ? "" : ", project"})\` when making significant architectural choices`);
|
|
3294
|
-
}
|
|
3295
|
-
if (isToolEnabled("pattern_create")) {
|
|
3296
|
-
workSteps.push("**Create patterns**: Call `pattern_create(title, description)` when establishing reusable conventions");
|
|
3297
|
-
}
|
|
3298
|
-
|
|
3299
|
-
if (workSteps.length > 0) {
|
|
3300
|
-
lines.push("While working:");
|
|
3301
|
-
const offset = startSteps.length;
|
|
3302
|
-
workSteps.forEach((step, i) => lines.push(`${offset + i + 1}. ${step}`));
|
|
3303
|
-
lines.push("");
|
|
3304
|
-
}
|
|
3305
|
-
|
|
3306
|
-
// When done section
|
|
3307
|
-
if (isToolEnabled("session_end")) {
|
|
3308
|
-
const offset = startSteps.length + workSteps.length;
|
|
3309
|
-
lines.push("When done:");
|
|
3310
|
-
lines.push(`${offset + 1}. **End session**: Call \`session_end(session_id, summary)\` with a summary of what was accomplished`);
|
|
3311
|
-
lines.push("");
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
|
-
// First-time setup — only if project tools are enabled
|
|
3315
|
-
if (isToolEnabled("project_register")) {
|
|
3316
|
-
lines.push("## First-Time Setup", "");
|
|
3317
|
-
lines.push("If the project is not yet registered, start with:");
|
|
3318
|
-
lines.push("1. `project_register(slug, name, description, stack)` to register the project");
|
|
3319
|
-
lines.push("2. Then follow the workflow above");
|
|
3320
|
-
lines.push("");
|
|
3321
|
-
if (isToolEnabled("project_list")) {
|
|
3322
|
-
lines.push("Use `project_list()` to see existing projects before registering a new one.");
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3325
|
-
|
|
3326
|
-
// Memory-only fallback — if no session/decision/project tools are enabled
|
|
3327
|
-
if (startSteps.length === 0 && workSteps.length === 0) {
|
|
3328
|
-
lines.push("Use `memory_store(content)` to save important information and `memory_recall(query)` to find relevant memories.");
|
|
3329
|
-
}
|
|
3330
|
-
|
|
3331
|
-
const workflowInstructions = lines.join("\n");
|
|
3332
|
-
|
|
3333
|
-
let prependContext = `<memoryrelay-workflow>\n${workflowInstructions}\n</memoryrelay-workflow>`;
|
|
3334
|
-
|
|
3335
|
-
// Auto-recall: search and inject relevant memories
|
|
3336
|
-
if (cfg?.autoRecall) {
|
|
3337
|
-
try {
|
|
3338
|
-
const results = await client.search(
|
|
3339
|
-
event.prompt,
|
|
3340
|
-
cfg.recallLimit || 5,
|
|
3341
|
-
cfg.recallThreshold || 0.3,
|
|
3342
|
-
);
|
|
3343
|
-
|
|
3344
|
-
if (results.length > 0) {
|
|
3345
|
-
const memoryContext = results.map((r) => `- ${r.memory.content}`).join("\n");
|
|
3346
|
-
|
|
3347
|
-
// Enhanced gateway logging (v0.8.2): Show memory previews
|
|
3348
|
-
if (cfg?.debug) {
|
|
3349
|
-
const snippets = results
|
|
3350
|
-
.map((r) => {
|
|
3351
|
-
const preview = r.memory.content.substring(0, 100).replace(/\n/g, ' ');
|
|
3352
|
-
const ellipsis = r.memory.content.length > 100 ? '...' : '';
|
|
3353
|
-
return ` • [${r.score.toFixed(2)}] ${preview}${ellipsis}`;
|
|
3354
|
-
})
|
|
3355
|
-
.join('\n');
|
|
3356
|
-
api.logger.info?.(
|
|
3357
|
-
`memory-memoryrelay: injecting ${results.length} memories into context:\n${snippets}`,
|
|
3358
|
-
);
|
|
3359
|
-
} else {
|
|
3360
|
-
api.logger.info?.(
|
|
3361
|
-
`memory-memoryrelay: injecting ${results.length} memories into context`,
|
|
3362
|
-
);
|
|
3363
|
-
}
|
|
3364
|
-
|
|
3365
|
-
prependContext +=
|
|
3366
|
-
`\n\n<relevant-memories>\nThe following memories from MemoryRelay may be relevant:\n${memoryContext}\n</relevant-memories>`;
|
|
3367
|
-
}
|
|
3368
|
-
} catch (err) {
|
|
3369
|
-
api.logger.warn?.(`memory-memoryrelay: recall failed: ${String(err)}`);
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
|
|
3373
|
-
return { prependContext };
|
|
3374
163
|
});
|
|
3375
164
|
|
|
3376
|
-
|
|
3377
|
-
if (cfg?.autoCapture) {
|
|
3378
|
-
api.on("agent_end", async (event) => {
|
|
3379
|
-
if (!event.success || !event.messages || event.messages.length === 0) {
|
|
3380
|
-
return;
|
|
3381
|
-
}
|
|
3382
|
-
|
|
3383
|
-
try {
|
|
3384
|
-
const texts: string[] = [];
|
|
3385
|
-
for (const msg of event.messages) {
|
|
3386
|
-
if (!msg || typeof msg !== "object") continue;
|
|
3387
|
-
const msgObj = msg as Record<string, unknown>;
|
|
3388
|
-
const role = msgObj.role;
|
|
3389
|
-
if (role !== "user" && role !== "assistant") continue;
|
|
3390
|
-
|
|
3391
|
-
const content = msgObj.content;
|
|
3392
|
-
if (typeof content === "string") {
|
|
3393
|
-
texts.push(content);
|
|
3394
|
-
} else if (Array.isArray(content)) {
|
|
3395
|
-
for (const block of content) {
|
|
3396
|
-
if (
|
|
3397
|
-
block &&
|
|
3398
|
-
typeof block === "object" &&
|
|
3399
|
-
"type" in block &&
|
|
3400
|
-
(block as Record<string, unknown>).type === "text" &&
|
|
3401
|
-
"text" in block
|
|
3402
|
-
) {
|
|
3403
|
-
texts.push((block as Record<string, unknown>).text as string);
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
|
|
3409
|
-
const toCapture = texts.filter((text) => text && shouldCapture(text));
|
|
3410
|
-
if (toCapture.length === 0) return;
|
|
3411
|
-
|
|
3412
|
-
let stored = 0;
|
|
3413
|
-
for (const text of toCapture.slice(0, 3)) {
|
|
3414
|
-
// Check for duplicates via search
|
|
3415
|
-
const existing = await client.search(text, 1, 0.95);
|
|
3416
|
-
if (existing.length > 0) continue;
|
|
3417
|
-
|
|
3418
|
-
await client.store(text, { source: "auto-capture" });
|
|
3419
|
-
stored++;
|
|
3420
|
-
}
|
|
3421
|
-
|
|
3422
|
-
if (stored > 0) {
|
|
3423
|
-
api.logger.info?.(`memory-memoryrelay: auto-captured ${stored} memories`);
|
|
3424
|
-
}
|
|
3425
|
-
} catch (err) {
|
|
3426
|
-
api.logger.warn?.(`memory-memoryrelay: capture failed: ${String(err)}`);
|
|
3427
|
-
}
|
|
3428
|
-
});
|
|
3429
|
-
}
|
|
3430
|
-
|
|
3431
|
-
api.logger.info?.(
|
|
3432
|
-
`memory-memoryrelay: plugin v0.8.0 loaded (39 tools, autoRecall: ${cfg?.autoRecall}, autoCapture: ${cfg?.autoCapture}, debug: ${debugEnabled})`,
|
|
3433
|
-
);
|
|
3434
|
-
|
|
3435
|
-
// ========================================================================
|
|
3436
|
-
// CLI Helper Tools (v0.8.0)
|
|
3437
|
-
// ========================================================================
|
|
3438
|
-
|
|
3439
|
-
// Register CLI-accessible tools for debugging and diagnostics
|
|
3440
|
-
|
|
3441
|
-
// memoryrelay:logs - Get debug logs
|
|
3442
|
-
if (debugLogger) {
|
|
3443
|
-
api.registerGatewayMethod?.("memoryrelay.logs", async ({ respond, args }) => {
|
|
3444
|
-
try {
|
|
3445
|
-
const limit = args?.limit || 20;
|
|
3446
|
-
const toolName = args?.tool;
|
|
3447
|
-
const errorsOnly = args?.errorsOnly || false;
|
|
3448
|
-
|
|
3449
|
-
let logs: LogEntry[];
|
|
3450
|
-
if (toolName) {
|
|
3451
|
-
logs = debugLogger.getToolLogs(toolName, limit);
|
|
3452
|
-
} else if (errorsOnly) {
|
|
3453
|
-
logs = debugLogger.getErrorLogs(limit);
|
|
3454
|
-
} else {
|
|
3455
|
-
logs = debugLogger.getRecentLogs(limit);
|
|
3456
|
-
}
|
|
3457
|
-
|
|
3458
|
-
const formatted = DebugLogger.formatTable(logs);
|
|
3459
|
-
respond(true, {
|
|
3460
|
-
logs,
|
|
3461
|
-
formatted,
|
|
3462
|
-
count: logs.length,
|
|
3463
|
-
});
|
|
3464
|
-
} catch (err) {
|
|
3465
|
-
respond(false, { error: String(err) });
|
|
3466
|
-
}
|
|
3467
|
-
});
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3470
|
-
// memoryrelay:health - Comprehensive health check
|
|
3471
|
-
api.registerGatewayMethod?.("memoryrelay.health", async ({ respond }) => {
|
|
3472
|
-
try {
|
|
3473
|
-
const startTime = Date.now();
|
|
3474
|
-
const health = await client.health();
|
|
3475
|
-
const healthDuration = Date.now() - startTime;
|
|
3476
|
-
|
|
3477
|
-
const results: any = {
|
|
3478
|
-
api: {
|
|
3479
|
-
status: health.status,
|
|
3480
|
-
endpoint: apiUrl,
|
|
3481
|
-
responseTime: healthDuration,
|
|
3482
|
-
reachable: true,
|
|
3483
|
-
},
|
|
3484
|
-
authentication: {
|
|
3485
|
-
status: "valid",
|
|
3486
|
-
apiKey: apiKey.substring(0, 16) + "...",
|
|
3487
|
-
},
|
|
3488
|
-
tools: {},
|
|
3489
|
-
};
|
|
3490
|
-
|
|
3491
|
-
// Test critical tools
|
|
3492
|
-
const toolTests = [
|
|
3493
|
-
{ name: "memory_store", test: async () => {
|
|
3494
|
-
const testMem = await client.store("Plugin health check test", { test: "true" });
|
|
3495
|
-
await client.delete(testMem.id);
|
|
3496
|
-
return { success: true };
|
|
3497
|
-
}},
|
|
3498
|
-
{ name: "memory_recall", test: async () => {
|
|
3499
|
-
await client.search("test", 1, 0.5);
|
|
3500
|
-
return { success: true };
|
|
3501
|
-
}},
|
|
3502
|
-
{ name: "memory_list", test: async () => {
|
|
3503
|
-
await client.list(1);
|
|
3504
|
-
return { success: true };
|
|
3505
|
-
}},
|
|
3506
|
-
];
|
|
3507
|
-
|
|
3508
|
-
for (const { name, test } of toolTests) {
|
|
3509
|
-
const testStart = Date.now();
|
|
3510
|
-
try {
|
|
3511
|
-
await test();
|
|
3512
|
-
results.tools[name] = {
|
|
3513
|
-
status: "working",
|
|
3514
|
-
duration: Date.now() - testStart,
|
|
3515
|
-
};
|
|
3516
|
-
} catch (err) {
|
|
3517
|
-
results.tools[name] = {
|
|
3518
|
-
status: "error",
|
|
3519
|
-
error: String(err),
|
|
3520
|
-
duration: Date.now() - testStart,
|
|
3521
|
-
};
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
|
|
3525
|
-
// Overall status
|
|
3526
|
-
const allToolsWorking = Object.values(results.tools).every(
|
|
3527
|
-
(t: any) => t.status === "working"
|
|
3528
|
-
);
|
|
3529
|
-
results.overall = allToolsWorking ? "healthy" : "degraded";
|
|
3530
|
-
|
|
3531
|
-
respond(true, results);
|
|
3532
|
-
} catch (err) {
|
|
3533
|
-
respond(false, {
|
|
3534
|
-
overall: "unhealthy",
|
|
3535
|
-
error: String(err),
|
|
3536
|
-
});
|
|
3537
|
-
}
|
|
3538
|
-
});
|
|
3539
|
-
|
|
3540
|
-
// memoryrelay:metrics - Performance metrics
|
|
3541
|
-
if (debugLogger) {
|
|
3542
|
-
api.registerGatewayMethod?.("memoryrelay.metrics", async ({ respond }) => {
|
|
3543
|
-
try {
|
|
3544
|
-
const stats = debugLogger.getStats();
|
|
3545
|
-
const allLogs = debugLogger.getAllLogs();
|
|
3546
|
-
|
|
3547
|
-
// Calculate per-tool metrics
|
|
3548
|
-
const toolMetrics: Record<string, any> = {};
|
|
3549
|
-
for (const log of allLogs) {
|
|
3550
|
-
if (!toolMetrics[log.tool]) {
|
|
3551
|
-
toolMetrics[log.tool] = {
|
|
3552
|
-
calls: 0,
|
|
3553
|
-
successes: 0,
|
|
3554
|
-
failures: 0,
|
|
3555
|
-
totalDuration: 0,
|
|
3556
|
-
durations: [],
|
|
3557
|
-
};
|
|
3558
|
-
}
|
|
3559
|
-
const metric = toolMetrics[log.tool];
|
|
3560
|
-
metric.calls++;
|
|
3561
|
-
if (log.status === "success") {
|
|
3562
|
-
metric.successes++;
|
|
3563
|
-
} else {
|
|
3564
|
-
metric.failures++;
|
|
3565
|
-
}
|
|
3566
|
-
metric.totalDuration += log.duration;
|
|
3567
|
-
metric.durations.push(log.duration);
|
|
3568
|
-
}
|
|
3569
|
-
|
|
3570
|
-
// Calculate averages and percentiles
|
|
3571
|
-
for (const tool in toolMetrics) {
|
|
3572
|
-
const metric = toolMetrics[tool];
|
|
3573
|
-
metric.avgDuration = Math.round(metric.totalDuration / metric.calls);
|
|
3574
|
-
metric.successRate = Math.round((metric.successes / metric.calls) * 100);
|
|
3575
|
-
|
|
3576
|
-
// Calculate p95 and p99
|
|
3577
|
-
const sorted = metric.durations.sort((a: number, b: number) => a - b);
|
|
3578
|
-
const p95Index = Math.floor(sorted.length * 0.95);
|
|
3579
|
-
const p99Index = Math.floor(sorted.length * 0.99);
|
|
3580
|
-
metric.p95Duration = sorted[p95Index] || 0;
|
|
3581
|
-
metric.p99Duration = sorted[p99Index] || 0;
|
|
3582
|
-
|
|
3583
|
-
delete metric.durations; // Don't include raw data in response
|
|
3584
|
-
}
|
|
3585
|
-
|
|
3586
|
-
respond(true, {
|
|
3587
|
-
summary: stats,
|
|
3588
|
-
toolMetrics,
|
|
3589
|
-
});
|
|
3590
|
-
} catch (err) {
|
|
3591
|
-
respond(false, { error: String(err) });
|
|
3592
|
-
}
|
|
3593
|
-
});
|
|
3594
|
-
}
|
|
3595
|
-
|
|
3596
|
-
// memoryrelay:test - Test individual tool
|
|
3597
|
-
api.registerGatewayMethod?.("memoryrelay.test", async ({ respond, args }) => {
|
|
3598
|
-
try {
|
|
3599
|
-
const toolName = args?.tool;
|
|
3600
|
-
if (!toolName) {
|
|
3601
|
-
respond(false, { error: "Missing required argument: tool" });
|
|
3602
|
-
return;
|
|
3603
|
-
}
|
|
3604
|
-
|
|
3605
|
-
const startTime = Date.now();
|
|
3606
|
-
let result: any;
|
|
3607
|
-
let error: string | undefined;
|
|
3608
|
-
|
|
3609
|
-
// Test the specified tool
|
|
3610
|
-
try {
|
|
3611
|
-
switch (toolName) {
|
|
3612
|
-
case "memory_store":
|
|
3613
|
-
const mem = await client.store("Test memory", { test: "true" });
|
|
3614
|
-
await client.delete(mem.id);
|
|
3615
|
-
result = { success: true, message: "Memory stored and deleted successfully" };
|
|
3616
|
-
break;
|
|
3617
|
-
|
|
3618
|
-
case "memory_recall":
|
|
3619
|
-
const searchResults = await client.search("test", 1, 0.5);
|
|
3620
|
-
result = { success: true, results: searchResults.length, message: "Search completed" };
|
|
3621
|
-
break;
|
|
3622
|
-
|
|
3623
|
-
case "memory_list":
|
|
3624
|
-
const list = await client.list(5);
|
|
3625
|
-
result = { success: true, count: list.length, message: "List retrieved" };
|
|
3626
|
-
break;
|
|
3627
|
-
|
|
3628
|
-
case "project_list":
|
|
3629
|
-
const projects = await client.listProjects(5);
|
|
3630
|
-
result = { success: true, count: projects.length, message: "Projects listed" };
|
|
3631
|
-
break;
|
|
3632
|
-
|
|
3633
|
-
case "memory_health":
|
|
3634
|
-
const health = await client.health();
|
|
3635
|
-
result = { success: true, status: health.status, message: "Health check passed" };
|
|
3636
|
-
break;
|
|
3637
|
-
|
|
3638
|
-
default:
|
|
3639
|
-
result = { success: false, message: `Unknown tool: ${toolName}` };
|
|
3640
|
-
}
|
|
3641
|
-
} catch (err) {
|
|
3642
|
-
error = String(err);
|
|
3643
|
-
result = { success: false, error };
|
|
3644
|
-
}
|
|
3645
|
-
|
|
3646
|
-
const duration = Date.now() - startTime;
|
|
3647
|
-
|
|
3648
|
-
respond(true, {
|
|
3649
|
-
tool: toolName,
|
|
3650
|
-
duration,
|
|
3651
|
-
result,
|
|
3652
|
-
error,
|
|
3653
|
-
});
|
|
3654
|
-
} catch (err) {
|
|
3655
|
-
respond(false, { error: String(err) });
|
|
3656
|
-
}
|
|
3657
|
-
});
|
|
165
|
+
api.logger.info(`memory-memoryrelay: connected to ${apiUrl}`);
|
|
3658
166
|
}
|