@memoryrelay/plugin-memoryrelay-ai 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,14 +2,14 @@
2
2
  "id": "plugin-memoryrelay-ai",
3
3
  "kind": "memory",
4
4
  "name": "MemoryRelay AI",
5
- "description": "AI memory service using MemoryRelay API (api.memoryrelay.net)",
6
- "version": "0.6.2",
5
+ "description": "AI memory service with sessions, decisions, patterns & projects (api.memoryrelay.net)",
6
+ "version": "0.7.0",
7
7
  "uiHints": {
8
8
  "apiKey": {
9
9
  "label": "MemoryRelay API Key",
10
10
  "sensitive": true,
11
11
  "placeholder": "mem_prod_...",
12
- "help": "API key from memoryrelay.io (or use ${MEMORYRELAY_API_KEY} env var)"
12
+ "help": "API key from memoryrelay.ai (or use ${MEMORYRELAY_API_KEY} env var)"
13
13
  },
14
14
  "agentId": {
15
15
  "label": "Agent ID",
@@ -22,6 +22,17 @@
22
22
  "advanced": true,
23
23
  "help": "MemoryRelay API endpoint (or use ${MEMORYRELAY_API_URL} env var)"
24
24
  },
25
+ "defaultProject": {
26
+ "label": "Default Project",
27
+ "placeholder": "my-api",
28
+ "help": "Default project slug for scoping sessions, decisions, and memories"
29
+ },
30
+ "enabledTools": {
31
+ "label": "Enabled Tools",
32
+ "placeholder": "memory,entity,agent,session,decision,pattern,project,health",
33
+ "advanced": true,
34
+ "help": "Comma-separated list of tool groups to enable (default: all)"
35
+ },
25
36
  "autoCapture": {
26
37
  "label": "Auto-Capture",
27
38
  "help": "Automatically capture important information from conversations"
@@ -54,6 +65,14 @@
54
65
  "type": "string",
55
66
  "default": "https://api.memoryrelay.net"
56
67
  },
68
+ "defaultProject": {
69
+ "type": "string",
70
+ "description": "Default project slug for scoping sessions, decisions, and memories"
71
+ },
72
+ "enabledTools": {
73
+ "type": "string",
74
+ "description": "Comma-separated tool groups to enable: memory, entity, agent, session, decision, pattern, project, health, or 'all' (default: all)"
75
+ },
57
76
  "autoCapture": {
58
77
  "type": "boolean",
59
78
  "default": false
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memoryrelay/plugin-memoryrelay-ai",
3
- "version": "0.6.2",
4
- "description": "OpenClaw memory plugin for MemoryRelay API - long-term memory with semantic search (ENHANCED)",
3
+ "version": "0.8.0",
4
+ "description": "OpenClaw memory plugin for MemoryRelay API - sessions, decisions, patterns, projects & semantic search",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "scripts": {
@@ -43,8 +43,16 @@
43
43
  "./index.ts"
44
44
  ]
45
45
  },
46
+ "bin": {
47
+ "memoryrelay-logs": "./bin/memoryrelay-logs.js",
48
+ "memoryrelay-health": "./bin/memoryrelay-health.js",
49
+ "memoryrelay-test": "./bin/memoryrelay-test.js",
50
+ "memoryrelay-metrics": "./bin/memoryrelay-metrics.js"
51
+ },
46
52
  "files": [
47
53
  "index.ts",
54
+ "src/",
55
+ "bin/",
48
56
  "openclaw.plugin.json",
49
57
  "README.md",
50
58
  "LICENSE"
@@ -0,0 +1,233 @@
1
+ /**
2
+ * DebugLogger Tests (Corrected)
3
+ *
4
+ * Tests matching actual implementation
5
+ */
6
+
7
+ import { describe, test, expect, beforeEach, vi } from "vitest";
8
+ import { DebugLogger, type LogEntry } from "./debug-logger";
9
+ import * as fs from "fs";
10
+
11
+ vi.mock("fs");
12
+
13
+ describe("DebugLogger", () => {
14
+ let logger: DebugLogger;
15
+
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ logger = new DebugLogger({
19
+ enabled: true,
20
+ verbose: false,
21
+ maxEntries: 5,
22
+ });
23
+ });
24
+
25
+ test("logs are stored when enabled", () => {
26
+ logger.log({
27
+ timestamp: new Date().toISOString(),
28
+ tool: "memory_store",
29
+ method: "POST",
30
+ path: "/v1/memories",
31
+ duration: 142,
32
+ status: "success",
33
+ });
34
+
35
+ const logs = logger.getAllLogs();
36
+ expect(logs).toHaveLength(1);
37
+ });
38
+
39
+ test("logs are not stored when disabled", () => {
40
+ const disabledLogger = new DebugLogger({
41
+ enabled: false,
42
+ verbose: false,
43
+ maxEntries: 5,
44
+ });
45
+
46
+ disabledLogger.log({
47
+ timestamp: new Date().toISOString(),
48
+ tool: "memory_store",
49
+ method: "POST",
50
+ path: "/v1/memories",
51
+ duration: 142,
52
+ status: "success",
53
+ });
54
+
55
+ const logs = disabledLogger.getAllLogs();
56
+ expect(logs).toHaveLength(0);
57
+ });
58
+
59
+ test("respects circular buffer limit (FIFO)", () => {
60
+ for (let i = 0; i < 10; i++) {
61
+ logger.log({
62
+ timestamp: new Date(Date.now() + i).toISOString(),
63
+ tool: `tool_${i}`,
64
+ method: "GET",
65
+ path: `/test/${i}`,
66
+ duration: 100,
67
+ status: "success",
68
+ });
69
+ }
70
+
71
+ const logs = logger.getAllLogs();
72
+ expect(logs).toHaveLength(5); // maxEntries = 5
73
+ expect(logs[0].tool).toBe("tool_5"); // Oldest kept
74
+ expect(logs[4].tool).toBe("tool_9"); // Newest
75
+ });
76
+
77
+ test("getRecentLogs returns last N entries", () => {
78
+ for (let i = 0; i < 3; i++) {
79
+ logger.log({
80
+ timestamp: new Date(Date.now() + i).toISOString(),
81
+ tool: `tool_${i}`,
82
+ method: "GET",
83
+ path: `/test/${i}`,
84
+ duration: 100,
85
+ status: "success",
86
+ });
87
+ }
88
+
89
+ const logs = logger.getRecentLogs(2);
90
+ expect(logs).toHaveLength(2);
91
+ expect(logs[0].tool).toBe("tool_1"); // Second-to-last
92
+ expect(logs[1].tool).toBe("tool_2"); // Last
93
+ });
94
+
95
+ test("getToolLogs filters by tool name", () => {
96
+ logger.log({
97
+ timestamp: new Date().toISOString(),
98
+ tool: "memory_store",
99
+ method: "POST",
100
+ path: "/v1/memories",
101
+ duration: 142,
102
+ status: "success",
103
+ });
104
+ logger.log({
105
+ timestamp: new Date().toISOString(),
106
+ tool: "memory_recall",
107
+ method: "POST",
108
+ path: "/v1/memories/search",
109
+ duration: 78,
110
+ status: "success",
111
+ });
112
+ logger.log({
113
+ timestamp: new Date().toISOString(),
114
+ tool: "memory_store",
115
+ method: "POST",
116
+ path: "/v1/memories",
117
+ duration: 156,
118
+ status: "error",
119
+ error: "500",
120
+ });
121
+
122
+ const logs = logger.getToolLogs("memory_store", 10);
123
+ expect(logs).toHaveLength(2);
124
+ expect(logs.every(l => l.tool === "memory_store")).toBe(true);
125
+ });
126
+
127
+ test("getErrorLogs filters by error status", () => {
128
+ logger.log({
129
+ timestamp: new Date().toISOString(),
130
+ tool: "memory_store",
131
+ method: "POST",
132
+ path: "/v1/memories",
133
+ duration: 142,
134
+ status: "success",
135
+ });
136
+ logger.log({
137
+ timestamp: new Date().toISOString(),
138
+ tool: "memory_recall",
139
+ method: "POST",
140
+ path: "/v1/memories/search",
141
+ duration: 78,
142
+ status: "error",
143
+ error: "404",
144
+ });
145
+
146
+ const logs = logger.getErrorLogs(10);
147
+ expect(logs).toHaveLength(1);
148
+ expect(logs[0].status).toBe("error");
149
+ });
150
+
151
+ test("getStats calculates correctly", () => {
152
+ logger.log({
153
+ timestamp: new Date().toISOString(),
154
+ tool: "memory_store",
155
+ method: "POST",
156
+ path: "/v1/memories",
157
+ duration: 100,
158
+ status: "success",
159
+ });
160
+ logger.log({
161
+ timestamp: new Date().toISOString(),
162
+ tool: "memory_recall",
163
+ method: "POST",
164
+ path: "/v1/memories/search",
165
+ duration: 200,
166
+ status: "success",
167
+ });
168
+ logger.log({
169
+ timestamp: new Date().toISOString(),
170
+ tool: "memory_store",
171
+ method: "POST",
172
+ path: "/v1/memories",
173
+ duration: 150,
174
+ status: "error",
175
+ error: "500",
176
+ });
177
+
178
+ const stats = logger.getStats();
179
+ expect(stats.total).toBe(3);
180
+ expect(stats.successful).toBe(2);
181
+ expect(stats.failed).toBe(1);
182
+ expect(stats.avgDuration).toBe(150); // (100+200+150)/3 = 150
183
+ });
184
+
185
+ test("clear() empties logs", () => {
186
+ logger.log({
187
+ timestamp: new Date().toISOString(),
188
+ tool: "memory_store",
189
+ method: "POST",
190
+ path: "/v1/memories",
191
+ duration: 142,
192
+ status: "success",
193
+ });
194
+
195
+ logger.clear();
196
+ const logs = logger.getAllLogs();
197
+ expect(logs).toHaveLength(0);
198
+ });
199
+
200
+ test("formatEntry creates human-readable output", () => {
201
+ const entry: LogEntry = {
202
+ timestamp: new Date().toISOString(),
203
+ tool: "memory_store",
204
+ method: "POST",
205
+ path: "/v1/memories",
206
+ duration: 142,
207
+ status: "success",
208
+ };
209
+
210
+ const formatted = DebugLogger.formatEntry(entry);
211
+ expect(formatted).toContain("memory_store");
212
+ expect(formatted).toContain("142ms");
213
+ expect(formatted).toContain("✓");
214
+ });
215
+
216
+ test("formatEntry shows error", () => {
217
+ const entry: LogEntry = {
218
+ timestamp: new Date().toISOString(),
219
+ tool: "memory_store",
220
+ method: "POST",
221
+ path: "/v1/memories",
222
+ duration: 156,
223
+ status: "error",
224
+ error: "500 Internal Server Error",
225
+ };
226
+
227
+ const formatted = DebugLogger.formatEntry(entry);
228
+ expect(formatted).toContain("memory_store");
229
+ expect(formatted).toContain("156ms");
230
+ expect(formatted).toContain("✗");
231
+ expect(formatted).toContain("500 Internal Server Error");
232
+ });
233
+ });
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Debug Logger for MemoryRelay OpenClaw Plugin
3
+ *
4
+ * Provides comprehensive logging of API calls with request/response capture
5
+ * for troubleshooting and performance analysis.
6
+ */
7
+
8
+ export interface LogEntry {
9
+ timestamp: string;
10
+ tool: string;
11
+ method: string;
12
+ path: string;
13
+ duration: number;
14
+ status: "success" | "error";
15
+ requestBody?: unknown;
16
+ responseBody?: unknown;
17
+ responseStatus?: number;
18
+ error?: string;
19
+ retries?: number;
20
+ }
21
+
22
+ export interface DebugLoggerConfig {
23
+ enabled: boolean;
24
+ verbose: boolean;
25
+ maxEntries: number;
26
+ logFile?: string;
27
+ }
28
+
29
+ export class DebugLogger {
30
+ private logs: LogEntry[] = [];
31
+ private config: DebugLoggerConfig;
32
+ private fs?: typeof import("fs");
33
+
34
+ constructor(config: DebugLoggerConfig) {
35
+ this.config = config;
36
+
37
+ // Only load fs if logFile is specified
38
+ if (config.logFile) {
39
+ try {
40
+ this.fs = require("fs");
41
+ } catch (err) {
42
+ console.warn("fs module not available, file logging disabled");
43
+ }
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Log an API call
49
+ */
50
+ log(entry: LogEntry): void {
51
+ if (!this.config.enabled) return;
52
+
53
+ // Add to in-memory buffer
54
+ this.logs.push(entry);
55
+
56
+ // Trim if exceeds max
57
+ if (this.logs.length > this.config.maxEntries) {
58
+ this.logs.shift();
59
+ }
60
+
61
+ // Write to file if configured
62
+ if (this.config.logFile && this.fs) {
63
+ this.writeToFile(entry);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get recent logs
69
+ */
70
+ getRecentLogs(limit: number = 10): LogEntry[] {
71
+ return this.logs.slice(-limit);
72
+ }
73
+
74
+ /**
75
+ * Get logs for specific tool
76
+ */
77
+ getToolLogs(toolName: string, limit: number = 10): LogEntry[] {
78
+ return this.logs
79
+ .filter(log => log.tool === toolName)
80
+ .slice(-limit);
81
+ }
82
+
83
+ /**
84
+ * Get error logs only
85
+ */
86
+ getErrorLogs(limit: number = 10): LogEntry[] {
87
+ return this.logs
88
+ .filter(log => log.status === "error")
89
+ .slice(-limit);
90
+ }
91
+
92
+ /**
93
+ * Get all logs
94
+ */
95
+ getAllLogs(): LogEntry[] {
96
+ return [...this.logs];
97
+ }
98
+
99
+ /**
100
+ * Clear logs
101
+ */
102
+ clear(): void {
103
+ this.logs = [];
104
+ }
105
+
106
+ /**
107
+ * Get statistics
108
+ */
109
+ getStats() {
110
+ const total = this.logs.length;
111
+ const successful = this.logs.filter(l => l.status === "success").length;
112
+ const failed = total - successful;
113
+ const avgDuration = total > 0
114
+ ? this.logs.reduce((sum, l) => sum + l.duration, 0) / total
115
+ : 0;
116
+
117
+ return {
118
+ total,
119
+ successful,
120
+ failed,
121
+ successRate: total > 0 ? (successful / total) * 100 : 0,
122
+ avgDuration: Math.round(avgDuration),
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Write log entry to file
128
+ */
129
+ private writeToFile(entry: LogEntry): void {
130
+ if (!this.fs || !this.config.logFile) return;
131
+
132
+ try {
133
+ const logLine = JSON.stringify(entry) + "\n";
134
+ this.fs.appendFileSync(this.config.logFile, logLine, "utf8");
135
+ } catch (err) {
136
+ console.error(`Failed to write debug log: ${err}`);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Format log entry for display
142
+ */
143
+ static formatEntry(entry: LogEntry): string {
144
+ const timestamp = new Date(entry.timestamp).toLocaleTimeString();
145
+ const status = entry.status === "success" ? "✓" : "✗";
146
+ const duration = `${entry.duration}ms`;
147
+
148
+ let output = `${timestamp} ${entry.tool.padEnd(20)} ${duration.padStart(6)} ${status}`;
149
+
150
+ if (entry.error) {
151
+ output += `\n Error: ${entry.error}`;
152
+ }
153
+
154
+ if (entry.retries && entry.retries > 0) {
155
+ output += ` (${entry.retries} retries)`;
156
+ }
157
+
158
+ return output;
159
+ }
160
+
161
+ /**
162
+ * Format logs as table
163
+ */
164
+ static formatTable(logs: LogEntry[]): string {
165
+ if (logs.length === 0) {
166
+ return "No logs available";
167
+ }
168
+
169
+ const lines = [
170
+ "TIMESTAMP TOOL DURATION STATUS ERROR",
171
+ "━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━ ━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━",
172
+ ];
173
+
174
+ for (const entry of logs) {
175
+ const timestamp = new Date(entry.timestamp).toLocaleTimeString();
176
+ const status = entry.status === "success" ? "✓" : "✗";
177
+ const duration = `${entry.duration}ms`;
178
+ const error = entry.error ? entry.error.substring(0, 30) : "";
179
+
180
+ lines.push(
181
+ `${timestamp} ${entry.tool.padEnd(20)} ${duration.padStart(8)} ${status.padEnd(6)} ${error}`
182
+ );
183
+ }
184
+
185
+ return lines.join("\n");
186
+ }
187
+ }