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