@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/README.md +278 -5
- package/bin/memoryrelay-health.js +46 -0
- package/bin/memoryrelay-logs.js +48 -0
- package/bin/memoryrelay-metrics.js +57 -0
- package/bin/memoryrelay-test.js +46 -0
- package/index.ts +442 -20
- package/package.json +9 -1
- package/src/debug-logger.test.ts +233 -0
- package/src/debug-logger.ts +187 -0
- package/src/status-reporter.test.ts +230 -0
- package/src/status-reporter.ts +284 -0
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenClaw Memory Plugin - MemoryRelay
|
|
3
|
-
* Version: 0.
|
|
3
|
+
* Version: 0.8.0 (Enhanced Debug & Status)
|
|
4
4
|
*
|
|
5
5
|
* Long-term memory with vector search using MemoryRelay API.
|
|
6
6
|
* Provides auto-recall and auto-capture via lifecycle hooks.
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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"
|