@memoryrelay/plugin-memoryrelay-ai 0.15.7 → 0.16.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.
Files changed (42) hide show
  1. package/index.ts +472 -4873
  2. package/openclaw.plugin.json +41 -3
  3. package/package.json +1 -1
  4. package/src/client/memoryrelay-client.ts +816 -0
  5. package/src/context/namespace-router.ts +19 -0
  6. package/src/context/request-context.ts +39 -0
  7. package/src/context/session-resolver.ts +93 -0
  8. package/src/filters/content-patterns.ts +32 -0
  9. package/src/filters/noise-patterns.ts +33 -0
  10. package/src/filters/non-interactive.ts +30 -0
  11. package/src/hooks/activity.ts +51 -0
  12. package/src/hooks/agent-end.ts +48 -0
  13. package/src/hooks/before-agent-start.ts +109 -0
  14. package/src/hooks/before-prompt-build.ts +46 -0
  15. package/src/hooks/compaction.ts +51 -0
  16. package/src/hooks/privacy.ts +44 -0
  17. package/src/hooks/session-lifecycle.ts +47 -0
  18. package/src/hooks/subagent.ts +62 -0
  19. package/src/pipelines/capture/content-strip.ts +14 -0
  20. package/src/pipelines/capture/dedup.ts +17 -0
  21. package/src/pipelines/capture/index.ts +13 -0
  22. package/src/pipelines/capture/message-filter.ts +16 -0
  23. package/src/pipelines/capture/store.ts +33 -0
  24. package/src/pipelines/capture/trigger-gate.ts +21 -0
  25. package/src/pipelines/capture/truncate.ts +16 -0
  26. package/src/pipelines/recall/format.ts +30 -0
  27. package/src/pipelines/recall/index.ts +12 -0
  28. package/src/pipelines/recall/rank.ts +40 -0
  29. package/src/pipelines/recall/scope-resolver.ts +20 -0
  30. package/src/pipelines/recall/search.ts +43 -0
  31. package/src/pipelines/recall/trigger-gate.ts +17 -0
  32. package/src/pipelines/runner.ts +25 -0
  33. package/src/pipelines/types.ts +157 -0
  34. package/src/tools/agent-tools.ts +127 -0
  35. package/src/tools/decision-tools.ts +309 -0
  36. package/src/tools/entity-tools.ts +215 -0
  37. package/src/tools/health-tools.ts +42 -0
  38. package/src/tools/memory-tools.ts +690 -0
  39. package/src/tools/pattern-tools.ts +250 -0
  40. package/src/tools/project-tools.ts +444 -0
  41. package/src/tools/session-tools.ts +195 -0
  42. package/src/tools/v2-tools.ts +228 -0
@@ -0,0 +1,250 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import type { PluginConfig } from "../pipelines/types.js";
3
+ import type { MemoryRelayClient } from "../client/memoryrelay-client.js";
4
+
5
+ export function registerPatternTools(
6
+ api: OpenClawPluginApi,
7
+ config: PluginConfig,
8
+ client: MemoryRelayClient,
9
+ isToolEnabled: (name: string) => boolean,
10
+ ): void {
11
+ const defaultProject = config.defaultProject;
12
+
13
+ // --------------------------------------------------------------------------
14
+ // 25. pattern_create
15
+ // --------------------------------------------------------------------------
16
+ if (isToolEnabled("pattern_create")) {
17
+ api.registerTool((ctx) => ({
18
+
19
+ name: "pattern_create",
20
+ description:
21
+ "Create a reusable pattern (coding convention, architecture pattern, or best practice) that can be shared across projects. Include example_code for maximum usefulness." +
22
+ (defaultProject ? ` Source project defaults to '${defaultProject}' if not specified.` : ""),
23
+ parameters: {
24
+ type: "object",
25
+ properties: {
26
+ title: {
27
+ type: "string",
28
+ description: "Pattern title.",
29
+ },
30
+ description: {
31
+ type: "string",
32
+ description: "Detailed description of the pattern, when to use it, and why.",
33
+ },
34
+ category: {
35
+ type: "string",
36
+ description: "Category (e.g., architecture, testing, error-handling, naming).",
37
+ },
38
+ example_code: {
39
+ type: "string",
40
+ description: "Example code demonstrating the pattern.",
41
+ },
42
+ scope: {
43
+ type: "string",
44
+ description: "Scope: global (visible to all projects) or project (visible to source project only).",
45
+ enum: ["global", "project"],
46
+ },
47
+ tags: {
48
+ type: "array",
49
+ description: "Tags for categorization.",
50
+ items: { type: "string" },
51
+ },
52
+ source_project: {
53
+ type: "string",
54
+ description: "Project slug where this pattern originated.",
55
+ },
56
+ },
57
+ required: ["title", "description"],
58
+ },
59
+ execute: async (
60
+ _id,
61
+ args: {
62
+ title: string;
63
+ description: string;
64
+ category?: string;
65
+ example_code?: string;
66
+ scope?: string;
67
+ tags?: string[];
68
+ source_project?: string;
69
+ },
70
+ ) => {
71
+ try {
72
+ const sourceProject = args.source_project ?? defaultProject;
73
+ const result = await client.createPattern(
74
+ args.title,
75
+ args.description,
76
+ args.category,
77
+ args.example_code,
78
+ args.scope,
79
+ args.tags,
80
+ sourceProject,
81
+ );
82
+ return {
83
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
84
+ details: { result },
85
+ };
86
+ } catch (err) {
87
+ return {
88
+ content: [{ type: "text", text: `Failed to create pattern: ${String(err)}` }],
89
+ details: { error: String(err) },
90
+ };
91
+ }
92
+ },
93
+ }),
94
+ { name: "pattern_create" },
95
+ );
96
+ }
97
+
98
+ // --------------------------------------------------------------------------
99
+ // 26. pattern_search
100
+ // --------------------------------------------------------------------------
101
+ if (isToolEnabled("pattern_search")) {
102
+ api.registerTool((ctx) => ({
103
+
104
+ name: "pattern_search",
105
+ description: "Search for established patterns by natural language query. Call this before writing code to find and follow existing conventions." +
106
+ (defaultProject ? ` Scoped to project '${defaultProject}' by default.` : ""),
107
+ parameters: {
108
+ type: "object",
109
+ properties: {
110
+ query: {
111
+ type: "string",
112
+ description: "Natural language search query.",
113
+ },
114
+ category: {
115
+ type: "string",
116
+ description: "Filter by category.",
117
+ },
118
+ project: {
119
+ type: "string",
120
+ description: "Filter by project slug.",
121
+ },
122
+ limit: {
123
+ type: "number",
124
+ description: "Maximum results. Default 10.",
125
+ },
126
+ threshold: {
127
+ type: "number",
128
+ description: "Minimum similarity threshold (0-1). Default 0.3.",
129
+ },
130
+ },
131
+ required: ["query"],
132
+ },
133
+ execute: async (
134
+ _id,
135
+ args: {
136
+ query: string;
137
+ category?: string;
138
+ project?: string;
139
+ limit?: number;
140
+ threshold?: number;
141
+ },
142
+ ) => {
143
+ try {
144
+ const project = args.project ?? defaultProject;
145
+ const result = await client.searchPatterns(
146
+ args.query,
147
+ args.category,
148
+ project,
149
+ args.limit,
150
+ args.threshold,
151
+ );
152
+ return {
153
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
154
+ details: { result },
155
+ };
156
+ } catch (err) {
157
+ return {
158
+ content: [{ type: "text", text: `Failed to search patterns: ${String(err)}` }],
159
+ details: { error: String(err) },
160
+ };
161
+ }
162
+ },
163
+ }),
164
+ { name: "pattern_search" },
165
+ );
166
+ }
167
+
168
+ // --------------------------------------------------------------------------
169
+ // 27. pattern_adopt
170
+ // --------------------------------------------------------------------------
171
+ if (isToolEnabled("pattern_adopt")) {
172
+ api.registerTool((ctx) => ({
173
+
174
+ name: "pattern_adopt",
175
+ description: "Adopt an existing pattern for use in a project. Creates a link between the pattern and the project.",
176
+ parameters: {
177
+ type: "object",
178
+ properties: {
179
+ id: {
180
+ type: "string",
181
+ description: "Pattern ID to adopt.",
182
+ },
183
+ project: {
184
+ type: "string",
185
+ description: "Project slug adopting the pattern.",
186
+ },
187
+ },
188
+ required: ["id", "project"],
189
+ },
190
+ execute: async (_id, args: { id: string; project: string }) => {
191
+ try {
192
+ const result = await client.adoptPattern(args.id, args.project);
193
+ return {
194
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
195
+ details: { result },
196
+ };
197
+ } catch (err) {
198
+ return {
199
+ content: [{ type: "text", text: `Failed to adopt pattern: ${String(err)}` }],
200
+ details: { error: String(err) },
201
+ };
202
+ }
203
+ },
204
+ }),
205
+ { name: "pattern_adopt" },
206
+ );
207
+ }
208
+
209
+ // --------------------------------------------------------------------------
210
+ // 28. pattern_suggest
211
+ // --------------------------------------------------------------------------
212
+ if (isToolEnabled("pattern_suggest")) {
213
+ api.registerTool((ctx) => ({
214
+
215
+ name: "pattern_suggest",
216
+ description:
217
+ "Get pattern suggestions for a project based on its stack and existing patterns from related projects.",
218
+ parameters: {
219
+ type: "object",
220
+ properties: {
221
+ project: {
222
+ type: "string",
223
+ description: "Project slug to get suggestions for.",
224
+ },
225
+ limit: {
226
+ type: "number",
227
+ description: "Maximum suggestions. Default 10.",
228
+ },
229
+ },
230
+ required: ["project"],
231
+ },
232
+ execute: async (_id, args: { project: string; limit?: number }) => {
233
+ try {
234
+ const result = await client.suggestPatterns(args.project, args.limit);
235
+ return {
236
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
237
+ details: { result },
238
+ };
239
+ } catch (err) {
240
+ return {
241
+ content: [{ type: "text", text: `Failed to suggest patterns: ${String(err)}` }],
242
+ details: { error: String(err) },
243
+ };
244
+ }
245
+ },
246
+ }),
247
+ { name: "pattern_suggest" },
248
+ );
249
+ }
250
+ }
@@ -0,0 +1,444 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import type { PluginConfig } from "../pipelines/types.js";
3
+ import type { MemoryRelayClient } from "../client/memoryrelay-client.js";
4
+
5
+ export function registerProjectTools(
6
+ api: OpenClawPluginApi,
7
+ config: PluginConfig,
8
+ client: MemoryRelayClient,
9
+ isToolEnabled: (name: string) => boolean,
10
+ ): void {
11
+
12
+ // --------------------------------------------------------------------------
13
+ // 29. project_register
14
+ // --------------------------------------------------------------------------
15
+ if (isToolEnabled("project_register")) {
16
+ api.registerTool((ctx) => ({
17
+
18
+ name: "project_register",
19
+ description: "Register a new project in MemoryRelay. Projects organize memories, decisions, patterns, and sessions.",
20
+ parameters: {
21
+ type: "object",
22
+ properties: {
23
+ slug: {
24
+ type: "string",
25
+ description: "URL-friendly project identifier (e.g., 'my-api', 'frontend-app').",
26
+ },
27
+ name: {
28
+ type: "string",
29
+ description: "Human-readable project name.",
30
+ },
31
+ description: {
32
+ type: "string",
33
+ description: "Project description.",
34
+ },
35
+ stack: {
36
+ type: "object",
37
+ description: "Technology stack details (e.g., {language: 'python', framework: 'fastapi'}).",
38
+ },
39
+ repo_url: {
40
+ type: "string",
41
+ description: "Repository URL.",
42
+ },
43
+ },
44
+ required: ["slug", "name"],
45
+ },
46
+ execute: async (
47
+ _id,
48
+ args: {
49
+ slug: string;
50
+ name: string;
51
+ description?: string;
52
+ stack?: Record<string, unknown>;
53
+ repo_url?: string;
54
+ },
55
+ ) => {
56
+ try {
57
+ const result = await client.registerProject(
58
+ args.slug,
59
+ args.name,
60
+ args.description,
61
+ args.stack,
62
+ args.repo_url,
63
+ );
64
+ return {
65
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
66
+ details: { result },
67
+ };
68
+ } catch (err) {
69
+ return {
70
+ content: [{ type: "text", text: `Failed to register project: ${String(err)}` }],
71
+ details: { error: String(err) },
72
+ };
73
+ }
74
+ },
75
+ }),
76
+ { name: "project_register" },
77
+ );
78
+ }
79
+
80
+ // --------------------------------------------------------------------------
81
+ // 30. project_list
82
+ // --------------------------------------------------------------------------
83
+ if (isToolEnabled("project_list")) {
84
+ api.registerTool((ctx) => ({
85
+
86
+ name: "project_list",
87
+ description: "List all registered projects.",
88
+ parameters: {
89
+ type: "object",
90
+ properties: {
91
+ limit: {
92
+ type: "number",
93
+ description: "Maximum projects to return. Default 20.",
94
+ minimum: 1,
95
+ maximum: 100,
96
+ },
97
+ },
98
+ },
99
+ execute: async (_id, args: { limit?: number }) => {
100
+ try {
101
+ const result = await client.listProjects(args.limit);
102
+ return {
103
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
104
+ details: { result },
105
+ };
106
+ } catch (err) {
107
+ return {
108
+ content: [{ type: "text", text: `Failed to list projects: ${String(err)}` }],
109
+ details: { error: String(err) },
110
+ };
111
+ }
112
+ },
113
+ }),
114
+ { name: "project_list" },
115
+ );
116
+ }
117
+
118
+ // --------------------------------------------------------------------------
119
+ // 31. project_info
120
+ // --------------------------------------------------------------------------
121
+ if (isToolEnabled("project_info")) {
122
+ api.registerTool((ctx) => ({
123
+
124
+ name: "project_info",
125
+ description: "Get detailed information about a specific project.",
126
+ parameters: {
127
+ type: "object",
128
+ properties: {
129
+ slug: {
130
+ type: "string",
131
+ description: "Project slug.",
132
+ },
133
+ },
134
+ required: ["slug"],
135
+ },
136
+ execute: async (_id, args: { slug: string }) => {
137
+ try {
138
+ const result = await client.getProject(args.slug);
139
+ return {
140
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
141
+ details: { result },
142
+ };
143
+ } catch (err) {
144
+ return {
145
+ content: [{ type: "text", text: `Failed to get project: ${String(err)}` }],
146
+ details: { error: String(err) },
147
+ };
148
+ }
149
+ },
150
+ }),
151
+ { name: "project_info" },
152
+ );
153
+ }
154
+
155
+ // --------------------------------------------------------------------------
156
+ // 32. project_add_relationship
157
+ // --------------------------------------------------------------------------
158
+ if (isToolEnabled("project_add_relationship")) {
159
+ api.registerTool((ctx) => ({
160
+
161
+ name: "project_add_relationship",
162
+ description:
163
+ "Add a relationship between two projects (e.g., depends_on, api_consumer, shares_schema, shares_infra, pattern_source, forked_from).",
164
+ parameters: {
165
+ type: "object",
166
+ properties: {
167
+ from: {
168
+ type: "string",
169
+ description: "Source project slug.",
170
+ },
171
+ to: {
172
+ type: "string",
173
+ description: "Target project slug.",
174
+ },
175
+ type: {
176
+ type: "string",
177
+ description: "Relationship type (e.g., depends_on, api_consumer, shares_schema, shares_infra, pattern_source, forked_from).",
178
+ },
179
+ metadata: {
180
+ type: "object",
181
+ description: "Optional metadata about the relationship.",
182
+ },
183
+ },
184
+ required: ["from", "to", "type"],
185
+ },
186
+ execute: async (
187
+ _id,
188
+ args: { from: string; to: string; type: string; metadata?: Record<string, unknown> },
189
+ ) => {
190
+ try {
191
+ const result = await client.addProjectRelationship(
192
+ args.from,
193
+ args.to,
194
+ args.type,
195
+ args.metadata,
196
+ );
197
+ return {
198
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
199
+ details: { result },
200
+ };
201
+ } catch (err) {
202
+ return {
203
+ content: [{ type: "text", text: `Failed to add relationship: ${String(err)}` }],
204
+ details: { error: String(err) },
205
+ };
206
+ }
207
+ },
208
+ }),
209
+ { name: "project_add_relationship" },
210
+ );
211
+ }
212
+
213
+ // --------------------------------------------------------------------------
214
+ // 33. project_dependencies
215
+ // --------------------------------------------------------------------------
216
+ if (isToolEnabled("project_dependencies")) {
217
+ api.registerTool((ctx) => ({
218
+
219
+ name: "project_dependencies",
220
+ description: "List projects that a given project depends on.",
221
+ parameters: {
222
+ type: "object",
223
+ properties: {
224
+ project: {
225
+ type: "string",
226
+ description: "Project slug.",
227
+ },
228
+ },
229
+ required: ["project"],
230
+ },
231
+ execute: async (_id, args: { project: string }) => {
232
+ try {
233
+ const result = await client.getProjectDependencies(args.project);
234
+ return {
235
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
236
+ details: { result },
237
+ };
238
+ } catch (err) {
239
+ return {
240
+ content: [{ type: "text", text: `Failed to get dependencies: ${String(err)}` }],
241
+ details: { error: String(err) },
242
+ };
243
+ }
244
+ },
245
+ }),
246
+ { name: "project_dependencies" },
247
+ );
248
+ }
249
+
250
+ // --------------------------------------------------------------------------
251
+ // 34. project_dependents
252
+ // --------------------------------------------------------------------------
253
+ if (isToolEnabled("project_dependents")) {
254
+ api.registerTool((ctx) => ({
255
+
256
+ name: "project_dependents",
257
+ description: "List projects that depend on a given project.",
258
+ parameters: {
259
+ type: "object",
260
+ properties: {
261
+ project: {
262
+ type: "string",
263
+ description: "Project slug.",
264
+ },
265
+ },
266
+ required: ["project"],
267
+ },
268
+ execute: async (_id, args: { project: string }) => {
269
+ try {
270
+ const result = await client.getProjectDependents(args.project);
271
+ return {
272
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
273
+ details: { result },
274
+ };
275
+ } catch (err) {
276
+ return {
277
+ content: [{ type: "text", text: `Failed to get dependents: ${String(err)}` }],
278
+ details: { error: String(err) },
279
+ };
280
+ }
281
+ },
282
+ }),
283
+ { name: "project_dependents" },
284
+ );
285
+ }
286
+
287
+ // --------------------------------------------------------------------------
288
+ // 35. project_related
289
+ // --------------------------------------------------------------------------
290
+ if (isToolEnabled("project_related")) {
291
+ api.registerTool((ctx) => ({
292
+
293
+ name: "project_related",
294
+ description: "List all projects related to a given project (any relationship direction).",
295
+ parameters: {
296
+ type: "object",
297
+ properties: {
298
+ project: {
299
+ type: "string",
300
+ description: "Project slug.",
301
+ },
302
+ },
303
+ required: ["project"],
304
+ },
305
+ execute: async (_id, args: { project: string }) => {
306
+ try {
307
+ const result = await client.getProjectRelated(args.project);
308
+ return {
309
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
310
+ details: { result },
311
+ };
312
+ } catch (err) {
313
+ return {
314
+ content: [{ type: "text", text: `Failed to get related projects: ${String(err)}` }],
315
+ details: { error: String(err) },
316
+ };
317
+ }
318
+ },
319
+ }),
320
+ { name: "project_related" },
321
+ );
322
+ }
323
+
324
+ // --------------------------------------------------------------------------
325
+ // 36. project_impact
326
+ // --------------------------------------------------------------------------
327
+ if (isToolEnabled("project_impact")) {
328
+ api.registerTool((ctx) => ({
329
+
330
+ name: "project_impact",
331
+ description:
332
+ "Analyze the impact of a proposed change on a project and its dependents. Helps understand blast radius before making changes.",
333
+ parameters: {
334
+ type: "object",
335
+ properties: {
336
+ project: {
337
+ type: "string",
338
+ description: "Project slug to analyze.",
339
+ },
340
+ change_description: {
341
+ type: "string",
342
+ description: "Description of the proposed change.",
343
+ },
344
+ },
345
+ required: ["project", "change_description"],
346
+ },
347
+ execute: async (_id, args: { project: string; change_description: string }) => {
348
+ try {
349
+ const result = await client.projectImpact(args.project, args.change_description);
350
+ return {
351
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
352
+ details: { result },
353
+ };
354
+ } catch (err) {
355
+ return {
356
+ content: [{ type: "text", text: `Failed to analyze impact: ${String(err)}` }],
357
+ details: { error: String(err) },
358
+ };
359
+ }
360
+ },
361
+ }),
362
+ { name: "project_impact" },
363
+ );
364
+ }
365
+
366
+ // --------------------------------------------------------------------------
367
+ // 37. project_shared_patterns
368
+ // --------------------------------------------------------------------------
369
+ if (isToolEnabled("project_shared_patterns")) {
370
+ api.registerTool((ctx) => ({
371
+
372
+ name: "project_shared_patterns",
373
+ description: "Find patterns shared between two projects. Useful for maintaining consistency across related projects.",
374
+ parameters: {
375
+ type: "object",
376
+ properties: {
377
+ project_a: {
378
+ type: "string",
379
+ description: "First project slug.",
380
+ },
381
+ project_b: {
382
+ type: "string",
383
+ description: "Second project slug.",
384
+ },
385
+ },
386
+ required: ["project_a", "project_b"],
387
+ },
388
+ execute: async (_id, args: { project_a: string; project_b: string }) => {
389
+ try {
390
+ const result = await client.getSharedPatterns(args.project_a, args.project_b);
391
+ return {
392
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
393
+ details: { result },
394
+ };
395
+ } catch (err) {
396
+ return {
397
+ content: [{ type: "text", text: `Failed to get shared patterns: ${String(err)}` }],
398
+ details: { error: String(err) },
399
+ };
400
+ }
401
+ },
402
+ }),
403
+ { name: "project_shared_patterns" },
404
+ );
405
+ }
406
+
407
+ // --------------------------------------------------------------------------
408
+ // 38. project_context
409
+ // --------------------------------------------------------------------------
410
+ if (isToolEnabled("project_context")) {
411
+ api.registerTool((ctx) => ({
412
+
413
+ name: "project_context",
414
+ description:
415
+ "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.",
416
+ parameters: {
417
+ type: "object",
418
+ properties: {
419
+ project: {
420
+ type: "string",
421
+ description: "Project slug.",
422
+ },
423
+ },
424
+ required: ["project"],
425
+ },
426
+ execute: async (_id, args: { project: string }) => {
427
+ try {
428
+ const result = await client.getProjectContext(args.project);
429
+ return {
430
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
431
+ details: { result },
432
+ };
433
+ } catch (err) {
434
+ return {
435
+ content: [{ type: "text", text: `Failed to load project context: ${String(err)}` }],
436
+ details: { error: String(err) },
437
+ };
438
+ }
439
+ },
440
+ }),
441
+ { name: "project_context" },
442
+ );
443
+ }
444
+ }