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