@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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusReporter Tests (Simplified)
|
|
3
|
+
*
|
|
4
|
+
* Tests matching actual implementation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect, beforeEach } from "vitest";
|
|
8
|
+
import { StatusReporter, type ConnectionStatus, type PluginConfig, type MemoryStats } from "./status-reporter";
|
|
9
|
+
import { DebugLogger } from "./debug-logger";
|
|
10
|
+
|
|
11
|
+
describe("StatusReporter", () => {
|
|
12
|
+
let reporter: StatusReporter;
|
|
13
|
+
let debugLogger: DebugLogger;
|
|
14
|
+
let mockConnection: ConnectionStatus;
|
|
15
|
+
let mockConfig: PluginConfig;
|
|
16
|
+
let mockStats: MemoryStats;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
debugLogger = new DebugLogger({
|
|
20
|
+
enabled: true,
|
|
21
|
+
verbose: false,
|
|
22
|
+
maxEntries: 100,
|
|
23
|
+
});
|
|
24
|
+
reporter = new StatusReporter(debugLogger);
|
|
25
|
+
|
|
26
|
+
mockConnection = {
|
|
27
|
+
status: "connected",
|
|
28
|
+
endpoint: "https://api.memoryrelay.net",
|
|
29
|
+
lastCheck: new Date().toISOString(),
|
|
30
|
+
responseTime: 45,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
mockConfig = {
|
|
34
|
+
agentId: "test-agent",
|
|
35
|
+
autoRecall: true,
|
|
36
|
+
autoCapture: false,
|
|
37
|
+
recallLimit: 5,
|
|
38
|
+
recallThreshold: 0.3,
|
|
39
|
+
excludeChannels: [],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
mockStats = {
|
|
43
|
+
total_memories: 100,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("records and clears tool failures", () => {
|
|
48
|
+
reporter.recordFailure("memory_store", "500 Error");
|
|
49
|
+
let issues = reporter.getIssues();
|
|
50
|
+
expect(issues).toHaveLength(1);
|
|
51
|
+
expect(issues[0].tool).toBe("memory_store");
|
|
52
|
+
|
|
53
|
+
reporter.recordSuccess("memory_store");
|
|
54
|
+
issues = reporter.getIssues();
|
|
55
|
+
expect(issues).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("buildReport creates status report", () => {
|
|
59
|
+
const toolGroups = {
|
|
60
|
+
"Core Memory": ["memory_store", "memory_recall"],
|
|
61
|
+
"Projects": ["project_list"],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const report = reporter.buildReport(
|
|
65
|
+
mockConnection,
|
|
66
|
+
mockConfig,
|
|
67
|
+
mockStats,
|
|
68
|
+
toolGroups,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(report.connection).toEqual(mockConnection);
|
|
72
|
+
expect(report.config).toEqual(mockConfig);
|
|
73
|
+
expect(report.stats).toEqual(mockStats);
|
|
74
|
+
expect(report.tools["Core Memory"]).toBeDefined();
|
|
75
|
+
expect(report.tools["Projects"]).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("buildReport includes tool status from debug logs", () => {
|
|
79
|
+
debugLogger.log({
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
tool: "memory_store",
|
|
82
|
+
method: "POST",
|
|
83
|
+
path: "/v1/memories",
|
|
84
|
+
duration: 142,
|
|
85
|
+
status: "success",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const toolGroups = {
|
|
89
|
+
"Core Memory": ["memory_store"],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const report = reporter.buildReport(
|
|
93
|
+
mockConnection,
|
|
94
|
+
mockConfig,
|
|
95
|
+
mockStats,
|
|
96
|
+
toolGroups,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const memoryTools = report.tools["Core Memory"];
|
|
100
|
+
expect(memoryTools.available).toBe(1);
|
|
101
|
+
expect(memoryTools.failed).toBe(0);
|
|
102
|
+
expect(memoryTools.tools[0].status).toBe("working");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("buildReport shows failed tools", () => {
|
|
106
|
+
debugLogger.log({
|
|
107
|
+
timestamp: new Date().toISOString(),
|
|
108
|
+
tool: "memory_store",
|
|
109
|
+
method: "POST",
|
|
110
|
+
path: "/v1/memories",
|
|
111
|
+
duration: 156,
|
|
112
|
+
status: "error",
|
|
113
|
+
error: "500 Internal Server Error",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const toolGroups = {
|
|
117
|
+
"Core Memory": ["memory_store"],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const report = reporter.buildReport(
|
|
121
|
+
mockConnection,
|
|
122
|
+
mockConfig,
|
|
123
|
+
mockStats,
|
|
124
|
+
toolGroups,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const memoryTools = report.tools["Core Memory"];
|
|
128
|
+
expect(memoryTools.available).toBe(0);
|
|
129
|
+
expect(memoryTools.failed).toBe(1);
|
|
130
|
+
expect(memoryTools.tools[0].status).toBe("error");
|
|
131
|
+
expect(memoryTools.tools[0].error).toBe("500 Internal Server Error");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("buildReport includes recent calls", () => {
|
|
135
|
+
debugLogger.log({
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
tool: "memory_store",
|
|
138
|
+
method: "POST",
|
|
139
|
+
path: "/v1/memories",
|
|
140
|
+
duration: 142,
|
|
141
|
+
status: "success",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const toolGroups = {
|
|
145
|
+
"Core Memory": ["memory_store"],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const report = reporter.buildReport(
|
|
149
|
+
mockConnection,
|
|
150
|
+
mockConfig,
|
|
151
|
+
mockStats,
|
|
152
|
+
toolGroups,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(report.recentCalls).toHaveLength(1);
|
|
156
|
+
expect(report.recentCalls[0].tool).toBe("memory_store");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("formatReport creates human-readable output", () => {
|
|
160
|
+
const toolGroups = {
|
|
161
|
+
"Core Memory": ["memory_store", "memory_recall"],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const report = reporter.buildReport(
|
|
165
|
+
mockConnection,
|
|
166
|
+
mockConfig,
|
|
167
|
+
mockStats,
|
|
168
|
+
toolGroups,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const formatted = StatusReporter.formatReport(report);
|
|
172
|
+
expect(formatted).toContain("MemoryRelay Plugin Status");
|
|
173
|
+
expect(formatted).toContain("connected");
|
|
174
|
+
expect(formatted).toContain("Core Memory");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("formatCompact creates brief output", () => {
|
|
178
|
+
const toolGroups = {
|
|
179
|
+
"Core Memory": ["memory_store"],
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const report = reporter.buildReport(
|
|
183
|
+
mockConnection,
|
|
184
|
+
mockConfig,
|
|
185
|
+
mockStats,
|
|
186
|
+
toolGroups,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const compact = StatusReporter.formatCompact(report);
|
|
190
|
+
expect(compact).toContain("connected");
|
|
191
|
+
expect(compact.length).toBeLessThan(200); // Should be brief
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("handles disconnected status", () => {
|
|
195
|
+
mockConnection.status = "disconnected";
|
|
196
|
+
|
|
197
|
+
const toolGroups = {
|
|
198
|
+
"Core Memory": ["memory_store"],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const report = reporter.buildReport(
|
|
202
|
+
mockConnection,
|
|
203
|
+
mockConfig,
|
|
204
|
+
mockStats,
|
|
205
|
+
toolGroups,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(report.connection.status).toBe("disconnected");
|
|
209
|
+
const formatted = StatusReporter.formatReport(report);
|
|
210
|
+
expect(formatted).toContain("disconnected");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("includes issues in report", () => {
|
|
214
|
+
reporter.recordFailure("memory_batch_store", "500 Error");
|
|
215
|
+
|
|
216
|
+
const toolGroups = {
|
|
217
|
+
"Core Memory": ["memory_batch_store"],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const report = reporter.buildReport(
|
|
221
|
+
mockConnection,
|
|
222
|
+
mockConfig,
|
|
223
|
+
mockStats,
|
|
224
|
+
toolGroups,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(report.issues).toHaveLength(1);
|
|
228
|
+
expect(report.issues[0].tool).toBe("memory_batch_store");
|
|
229
|
+
});
|
|
230
|
+
});
|