@memoryrelay/plugin-memoryrelay-ai 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * OpenClaw Memory Plugin - MemoryRelay
3
- * Version: 0.7.0 (Full Suite)
3
+ * Version: 0.8.0 (Enhanced Debug & Status)
4
4
  *
5
5
  * Long-term memory with vector search using MemoryRelay API.
6
6
  * Provides auto-recall and auto-capture via lifecycle hooks.
@@ -9,6 +9,15 @@
9
9
  * API: https://api.memoryrelay.net
10
10
  * Docs: https://memoryrelay.ai
11
11
  *
12
+ * ENHANCEMENTS (v0.8.0):
13
+ * - Debug mode with comprehensive API call logging
14
+ * - Enhanced status reporting with tool breakdown
15
+ * - Request/response capture (verbose mode)
16
+ * - Tool failure tracking and known issues display
17
+ * - Performance metrics (duration, success rate)
18
+ * - Recent activity display
19
+ * - Formatted CLI output with Unicode symbols
20
+ *
12
21
  * ENHANCEMENTS (v0.7.0):
13
22
  * - 39 tools covering all MemoryRelay API resources
14
23
  * - Session tracking, decision logging, pattern management, project context
@@ -21,6 +30,8 @@
21
30
  */
22
31
 
23
32
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
33
+ import { DebugLogger, type LogEntry } from "./src/debug-logger";
34
+ import { StatusReporter } from "./src/status-reporter";
24
35
 
25
36
  // ============================================================================
26
37
  // Constants
@@ -47,6 +58,11 @@ interface MemoryRelayConfig {
47
58
  excludeChannels?: string[];
48
59
  defaultProject?: string;
49
60
  enabledTools?: string;
61
+ // Debug and logging options (v0.8.0)
62
+ debug?: boolean;
63
+ verbose?: boolean;
64
+ logFile?: string;
65
+ maxLogEntries?: number;
50
66
  }
51
67
 
52
68
  interface Memory {
@@ -130,11 +146,41 @@ async function fetchWithTimeout(
130
146
  // ============================================================================
131
147
 
132
148
  class MemoryRelayClient {
149
+ private debugLogger?: DebugLogger;
150
+ private statusReporter?: StatusReporter;
151
+
133
152
  constructor(
134
153
  private readonly apiKey: string,
135
154
  private readonly agentId: string,
136
155
  private readonly apiUrl: string = DEFAULT_API_URL,
137
- ) {}
156
+ debugLogger?: DebugLogger,
157
+ statusReporter?: StatusReporter,
158
+ ) {
159
+ this.debugLogger = debugLogger;
160
+ this.statusReporter = statusReporter;
161
+ }
162
+
163
+ /**
164
+ * Extract tool name from API path
165
+ */
166
+ private extractToolName(path: string): string {
167
+ // /v1/memories -> memory
168
+ // /v1/memories/batch -> memory_batch
169
+ // /v1/sessions/123/end -> session_end
170
+ const parts = path.split("/").filter(Boolean);
171
+ if (parts.length < 2) return "unknown";
172
+
173
+ let toolName = parts[1].replace(/s$/, ""); // Remove trailing 's'
174
+
175
+ // Check for specific endpoints
176
+ if (path.includes("/batch")) toolName += "_batch";
177
+ if (path.includes("/recall")) toolName += "_recall";
178
+ if (path.includes("/context")) toolName += "_context";
179
+ if (path.includes("/end")) toolName += "_end";
180
+ if (path.includes("/health")) return "memory_health";
181
+
182
+ return toolName;
183
+ }
138
184
 
139
185
  /**
140
186
  * Make HTTP request with retry logic and timeout
@@ -146,6 +192,8 @@ class MemoryRelayClient {
146
192
  retryCount = 0,
147
193
  ): Promise<T> {
148
194
  const url = `${this.apiUrl}${path}`;
195
+ const startTime = Date.now();
196
+ const toolName = this.extractToolName(path);
149
197
 
150
198
  try {
151
199
  const response = await fetchWithTimeout(
@@ -155,13 +203,15 @@ class MemoryRelayClient {
155
203
  headers: {
156
204
  "Content-Type": "application/json",
157
205
  Authorization: `Bearer ${this.apiKey}`,
158
- "User-Agent": "openclaw-memory-memoryrelay/0.7.0",
206
+ "User-Agent": "openclaw-memory-memoryrelay/0.8.0",
159
207
  },
160
208
  body: body ? JSON.stringify(body) : undefined,
161
209
  },
162
210
  REQUEST_TIMEOUT_MS,
163
211
  );
164
212
 
213
+ const duration = Date.now() - startTime;
214
+
165
215
  if (!response.ok) {
166
216
  const errorData = await response.json().catch(() => ({}));
167
217
  const errorMsg = errorData.detail || errorData.message || "";
@@ -170,6 +220,27 @@ class MemoryRelayClient {
170
220
  (errorMsg ? ` - ${errorMsg}` : ""),
171
221
  );
172
222
 
223
+ // Log error
224
+ if (this.debugLogger) {
225
+ this.debugLogger.log({
226
+ timestamp: new Date().toISOString(),
227
+ tool: toolName,
228
+ method,
229
+ path,
230
+ duration,
231
+ status: "error",
232
+ responseStatus: response.status,
233
+ error: error.message,
234
+ retries: retryCount,
235
+ requestBody: this.debugLogger && body ? body : undefined,
236
+ });
237
+ }
238
+
239
+ // Track failure
240
+ if (this.statusReporter) {
241
+ this.statusReporter.recordFailure(toolName, `${response.status} ${errorMsg || response.statusText}`);
242
+ }
243
+
173
244
  // Retry on 5xx errors
174
245
  if (response.status >= 500 && retryCount < MAX_RETRIES) {
175
246
  const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
@@ -180,8 +251,53 @@ class MemoryRelayClient {
180
251
  throw error;
181
252
  }
182
253
 
183
- return response.json();
254
+ const result = await response.json();
255
+
256
+ // Log success
257
+ if (this.debugLogger) {
258
+ this.debugLogger.log({
259
+ timestamp: new Date().toISOString(),
260
+ tool: toolName,
261
+ method,
262
+ path,
263
+ duration,
264
+ status: "success",
265
+ responseStatus: response.status,
266
+ retries: retryCount,
267
+ requestBody: this.debugLogger && body ? body : undefined,
268
+ responseBody: this.debugLogger && result ? result : undefined,
269
+ });
270
+ }
271
+
272
+ // Track success
273
+ if (this.statusReporter) {
274
+ this.statusReporter.recordSuccess(toolName);
275
+ }
276
+
277
+ return result;
184
278
  } catch (err) {
279
+ const duration = Date.now() - startTime;
280
+
281
+ // Log error
282
+ if (this.debugLogger) {
283
+ this.debugLogger.log({
284
+ timestamp: new Date().toISOString(),
285
+ tool: toolName,
286
+ method,
287
+ path,
288
+ duration,
289
+ status: "error",
290
+ error: String(err),
291
+ retries: retryCount,
292
+ requestBody: this.debugLogger && body ? body : undefined,
293
+ });
294
+ }
295
+
296
+ // Track failure
297
+ if (this.statusReporter) {
298
+ this.statusReporter.recordFailure(toolName, String(err));
299
+ }
300
+
185
301
  // Retry on network errors
186
302
  if (isRetryableError(err) && retryCount < MAX_RETRIES) {
187
303
  const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
@@ -694,7 +810,32 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
694
810
 
695
811
  const apiUrl = cfg?.apiUrl || process.env.MEMORYRELAY_API_URL || DEFAULT_API_URL;
696
812
  const defaultProject = cfg?.defaultProject || process.env.MEMORYRELAY_DEFAULT_PROJECT;
697
- const client = new MemoryRelayClient(apiKey, agentId, apiUrl);
813
+
814
+ // ========================================================================
815
+ // Debug Logger and Status Reporter (v0.8.0)
816
+ // ========================================================================
817
+
818
+ const debugEnabled = cfg?.debug || false;
819
+ const verboseEnabled = cfg?.verbose || false;
820
+ const logFile = cfg?.logFile;
821
+ const maxLogEntries = cfg?.maxLogEntries || 100;
822
+
823
+ let debugLogger: DebugLogger | undefined;
824
+ let statusReporter: StatusReporter | undefined;
825
+
826
+ if (debugEnabled) {
827
+ debugLogger = new DebugLogger({
828
+ enabled: true,
829
+ verbose: verboseEnabled,
830
+ maxEntries: maxLogEntries,
831
+ logFile: logFile,
832
+ });
833
+ api.logger.info(`memory-memoryrelay: debug mode enabled (verbose: ${verboseEnabled}, maxEntries: ${maxLogEntries})`);
834
+ }
835
+
836
+ statusReporter = new StatusReporter(debugLogger);
837
+
838
+ const client = new MemoryRelayClient(apiKey, agentId, apiUrl, debugLogger, statusReporter);
698
839
 
699
840
  // Verify connection on startup (with timeout)
700
841
  try {
@@ -711,30 +852,87 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
711
852
 
712
853
  api.registerGatewayMethod?.("memory.status", async ({ respond }) => {
713
854
  try {
855
+ // Get connection status
856
+ const startTime = Date.now();
714
857
  const health = await client.health();
858
+ const responseTime = Date.now() - startTime;
859
+
860
+ const healthStatus = String(health.status).toLowerCase();
861
+ const isConnected = VALID_HEALTH_STATUSES.includes(healthStatus);
862
+
863
+ const connectionStatus = {
864
+ status: isConnected ? "connected" as const : "disconnected" as const,
865
+ endpoint: apiUrl,
866
+ lastCheck: new Date().toISOString(),
867
+ responseTime,
868
+ };
869
+
870
+ // Get memory stats
715
871
  let memoryCount = 0;
716
-
717
872
  try {
718
873
  const stats = await client.stats();
719
874
  memoryCount = stats.total_memories;
720
875
  } catch (statsErr) {
721
876
  api.logger.debug?.(`memory-memoryrelay: stats endpoint unavailable: ${String(statsErr)}`);
722
877
  }
723
-
724
- const healthStatus = String(health.status).toLowerCase();
725
- const isConnected = VALID_HEALTH_STATUSES.includes(healthStatus);
726
-
727
- respond(true, {
728
- available: true,
729
- connected: isConnected,
730
- endpoint: apiUrl,
731
- memoryCount: memoryCount,
878
+
879
+ const memoryStats = {
880
+ total_memories: memoryCount,
881
+ };
882
+
883
+ // Get config
884
+ const pluginConfig = {
732
885
  agentId: agentId,
733
- vector: {
886
+ autoRecall: cfg?.autoRecall ?? true,
887
+ autoCapture: cfg?.autoCapture ?? false,
888
+ recallLimit: cfg?.recallLimit ?? 5,
889
+ recallThreshold: cfg?.recallThreshold ?? 0.3,
890
+ excludeChannels: cfg?.excludeChannels ?? [],
891
+ defaultProject: defaultProject,
892
+ };
893
+
894
+ // Build comprehensive status report
895
+ if (statusReporter) {
896
+ const report = statusReporter.buildReport(
897
+ connectionStatus,
898
+ pluginConfig,
899
+ memoryStats,
900
+ TOOL_GROUPS,
901
+ );
902
+
903
+ // Format and output
904
+ const formatted = StatusReporter.formatReport(report);
905
+ api.logger.info(formatted);
906
+
907
+ // Also return structured data for programmatic access
908
+ respond(true, {
734
909
  available: true,
735
- enabled: true,
736
- },
737
- });
910
+ connected: isConnected,
911
+ endpoint: apiUrl,
912
+ memoryCount: memoryCount,
913
+ agentId: agentId,
914
+ debug: debugEnabled,
915
+ verbose: verboseEnabled,
916
+ report: report,
917
+ vector: {
918
+ available: true,
919
+ enabled: true,
920
+ },
921
+ });
922
+ } else {
923
+ // Fallback to simple status (shouldn't happen)
924
+ respond(true, {
925
+ available: true,
926
+ connected: isConnected,
927
+ endpoint: apiUrl,
928
+ memoryCount: memoryCount,
929
+ agentId: agentId,
930
+ vector: {
931
+ available: true,
932
+ enabled: true,
933
+ },
934
+ });
935
+ }
738
936
  } catch (err) {
739
937
  respond(true, {
740
938
  available: false,
@@ -3170,6 +3368,230 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
3170
3368
  }
3171
3369
 
3172
3370
  api.logger.info?.(
3173
- `memory-memoryrelay: plugin v0.7.0 loaded (39 tools, autoRecall: ${cfg?.autoRecall}, autoCapture: ${cfg?.autoCapture})`,
3371
+ `memory-memoryrelay: plugin v0.8.0 loaded (39 tools, autoRecall: ${cfg?.autoRecall}, autoCapture: ${cfg?.autoCapture}, debug: ${debugEnabled})`,
3174
3372
  );
3373
+
3374
+ // ========================================================================
3375
+ // CLI Helper Tools (v0.8.0)
3376
+ // ========================================================================
3377
+
3378
+ // Register CLI-accessible tools for debugging and diagnostics
3379
+
3380
+ // memoryrelay:logs - Get debug logs
3381
+ if (debugLogger) {
3382
+ api.registerGatewayMethod?.("memoryrelay.logs", async ({ respond, args }) => {
3383
+ try {
3384
+ const limit = args?.limit || 20;
3385
+ const toolName = args?.tool;
3386
+ const errorsOnly = args?.errorsOnly || false;
3387
+
3388
+ let logs: LogEntry[];
3389
+ if (toolName) {
3390
+ logs = debugLogger.getToolLogs(toolName, limit);
3391
+ } else if (errorsOnly) {
3392
+ logs = debugLogger.getErrorLogs(limit);
3393
+ } else {
3394
+ logs = debugLogger.getRecentLogs(limit);
3395
+ }
3396
+
3397
+ const formatted = DebugLogger.formatTable(logs);
3398
+ respond(true, {
3399
+ logs,
3400
+ formatted,
3401
+ count: logs.length,
3402
+ });
3403
+ } catch (err) {
3404
+ respond(false, { error: String(err) });
3405
+ }
3406
+ });
3407
+ }
3408
+
3409
+ // memoryrelay:health - Comprehensive health check
3410
+ api.registerGatewayMethod?.("memoryrelay.health", async ({ respond }) => {
3411
+ try {
3412
+ const startTime = Date.now();
3413
+ const health = await client.health();
3414
+ const healthDuration = Date.now() - startTime;
3415
+
3416
+ const results: any = {
3417
+ api: {
3418
+ status: health.status,
3419
+ endpoint: apiUrl,
3420
+ responseTime: healthDuration,
3421
+ reachable: true,
3422
+ },
3423
+ authentication: {
3424
+ status: "valid",
3425
+ apiKey: apiKey.substring(0, 16) + "...",
3426
+ },
3427
+ tools: {},
3428
+ };
3429
+
3430
+ // Test critical tools
3431
+ const toolTests = [
3432
+ { name: "memory_store", test: async () => {
3433
+ const testMem = await client.store("Plugin health check test", { test: "true" });
3434
+ await client.delete(testMem.id);
3435
+ return { success: true };
3436
+ }},
3437
+ { name: "memory_recall", test: async () => {
3438
+ await client.search("test", 1, 0.5);
3439
+ return { success: true };
3440
+ }},
3441
+ { name: "memory_list", test: async () => {
3442
+ await client.list(1);
3443
+ return { success: true };
3444
+ }},
3445
+ ];
3446
+
3447
+ for (const { name, test } of toolTests) {
3448
+ const testStart = Date.now();
3449
+ try {
3450
+ await test();
3451
+ results.tools[name] = {
3452
+ status: "working",
3453
+ duration: Date.now() - testStart,
3454
+ };
3455
+ } catch (err) {
3456
+ results.tools[name] = {
3457
+ status: "error",
3458
+ error: String(err),
3459
+ duration: Date.now() - testStart,
3460
+ };
3461
+ }
3462
+ }
3463
+
3464
+ // Overall status
3465
+ const allToolsWorking = Object.values(results.tools).every(
3466
+ (t: any) => t.status === "working"
3467
+ );
3468
+ results.overall = allToolsWorking ? "healthy" : "degraded";
3469
+
3470
+ respond(true, results);
3471
+ } catch (err) {
3472
+ respond(false, {
3473
+ overall: "unhealthy",
3474
+ error: String(err),
3475
+ });
3476
+ }
3477
+ });
3478
+
3479
+ // memoryrelay:metrics - Performance metrics
3480
+ if (debugLogger) {
3481
+ api.registerGatewayMethod?.("memoryrelay.metrics", async ({ respond }) => {
3482
+ try {
3483
+ const stats = debugLogger.getStats();
3484
+ const allLogs = debugLogger.getAllLogs();
3485
+
3486
+ // Calculate per-tool metrics
3487
+ const toolMetrics: Record<string, any> = {};
3488
+ for (const log of allLogs) {
3489
+ if (!toolMetrics[log.tool]) {
3490
+ toolMetrics[log.tool] = {
3491
+ calls: 0,
3492
+ successes: 0,
3493
+ failures: 0,
3494
+ totalDuration: 0,
3495
+ durations: [],
3496
+ };
3497
+ }
3498
+ const metric = toolMetrics[log.tool];
3499
+ metric.calls++;
3500
+ if (log.status === "success") {
3501
+ metric.successes++;
3502
+ } else {
3503
+ metric.failures++;
3504
+ }
3505
+ metric.totalDuration += log.duration;
3506
+ metric.durations.push(log.duration);
3507
+ }
3508
+
3509
+ // Calculate averages and percentiles
3510
+ for (const tool in toolMetrics) {
3511
+ const metric = toolMetrics[tool];
3512
+ metric.avgDuration = Math.round(metric.totalDuration / metric.calls);
3513
+ metric.successRate = Math.round((metric.successes / metric.calls) * 100);
3514
+
3515
+ // Calculate p95 and p99
3516
+ const sorted = metric.durations.sort((a: number, b: number) => a - b);
3517
+ const p95Index = Math.floor(sorted.length * 0.95);
3518
+ const p99Index = Math.floor(sorted.length * 0.99);
3519
+ metric.p95Duration = sorted[p95Index] || 0;
3520
+ metric.p99Duration = sorted[p99Index] || 0;
3521
+
3522
+ delete metric.durations; // Don't include raw data in response
3523
+ }
3524
+
3525
+ respond(true, {
3526
+ summary: stats,
3527
+ toolMetrics,
3528
+ });
3529
+ } catch (err) {
3530
+ respond(false, { error: String(err) });
3531
+ }
3532
+ });
3533
+ }
3534
+
3535
+ // memoryrelay:test - Test individual tool
3536
+ api.registerGatewayMethod?.("memoryrelay.test", async ({ respond, args }) => {
3537
+ try {
3538
+ const toolName = args?.tool;
3539
+ if (!toolName) {
3540
+ respond(false, { error: "Missing required argument: tool" });
3541
+ return;
3542
+ }
3543
+
3544
+ const startTime = Date.now();
3545
+ let result: any;
3546
+ let error: string | undefined;
3547
+
3548
+ // Test the specified tool
3549
+ try {
3550
+ switch (toolName) {
3551
+ case "memory_store":
3552
+ const mem = await client.store("Test memory", { test: "true" });
3553
+ await client.delete(mem.id);
3554
+ result = { success: true, message: "Memory stored and deleted successfully" };
3555
+ break;
3556
+
3557
+ case "memory_recall":
3558
+ const searchResults = await client.search("test", 1, 0.5);
3559
+ result = { success: true, results: searchResults.length, message: "Search completed" };
3560
+ break;
3561
+
3562
+ case "memory_list":
3563
+ const list = await client.list(5);
3564
+ result = { success: true, count: list.length, message: "List retrieved" };
3565
+ break;
3566
+
3567
+ case "project_list":
3568
+ const projects = await client.listProjects(5);
3569
+ result = { success: true, count: projects.length, message: "Projects listed" };
3570
+ break;
3571
+
3572
+ case "memory_health":
3573
+ const health = await client.health();
3574
+ result = { success: true, status: health.status, message: "Health check passed" };
3575
+ break;
3576
+
3577
+ default:
3578
+ result = { success: false, message: `Unknown tool: ${toolName}` };
3579
+ }
3580
+ } catch (err) {
3581
+ error = String(err);
3582
+ result = { success: false, error };
3583
+ }
3584
+
3585
+ const duration = Date.now() - startTime;
3586
+
3587
+ respond(true, {
3588
+ tool: toolName,
3589
+ duration,
3590
+ result,
3591
+ error,
3592
+ });
3593
+ } catch (err) {
3594
+ respond(false, { error: String(err) });
3595
+ }
3596
+ });
3175
3597
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoryrelay/plugin-memoryrelay-ai",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "OpenClaw memory plugin for MemoryRelay API - sessions, decisions, patterns, projects & semantic search",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -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"