@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.
- package/README.md +427 -171
- 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 +3033 -221
- package/openclaw.plugin.json +22 -3
- package/package.json +10 -2
- 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,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
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Reporter for MemoryRelay OpenClaw Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive status reporting for openclaw status command
|
|
5
|
+
* including connection status, tool breakdown, and recent activity.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LogEntry, DebugLogger } from "./debug-logger";
|
|
9
|
+
|
|
10
|
+
export interface ToolStatus {
|
|
11
|
+
enabled: number;
|
|
12
|
+
available: number;
|
|
13
|
+
failed: number;
|
|
14
|
+
tools: {
|
|
15
|
+
name: string;
|
|
16
|
+
status: "working" | "error" | "unknown";
|
|
17
|
+
error?: string;
|
|
18
|
+
lastSuccess?: string;
|
|
19
|
+
lastError?: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ConnectionStatus {
|
|
24
|
+
status: "connected" | "disconnected" | "degraded";
|
|
25
|
+
endpoint: string;
|
|
26
|
+
lastCheck: string;
|
|
27
|
+
responseTime: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MemoryStats {
|
|
31
|
+
total_memories: number;
|
|
32
|
+
memories_today?: number;
|
|
33
|
+
last_stored?: string;
|
|
34
|
+
search_count_24h?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PluginConfig {
|
|
38
|
+
agentId: string;
|
|
39
|
+
autoRecall: boolean;
|
|
40
|
+
autoCapture: boolean;
|
|
41
|
+
recallLimit: number;
|
|
42
|
+
recallThreshold: number;
|
|
43
|
+
excludeChannels: string[];
|
|
44
|
+
defaultProject?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StatusReport {
|
|
48
|
+
connection: ConnectionStatus;
|
|
49
|
+
config: PluginConfig;
|
|
50
|
+
stats: MemoryStats;
|
|
51
|
+
tools: Record<string, ToolStatus>;
|
|
52
|
+
recentCalls: LogEntry[];
|
|
53
|
+
issues: { tool: string; error: string; since: string }[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class StatusReporter {
|
|
57
|
+
private debugLogger?: DebugLogger;
|
|
58
|
+
private toolFailures: Map<string, { error: string; since: string }> = new Map();
|
|
59
|
+
|
|
60
|
+
constructor(debugLogger?: DebugLogger) {
|
|
61
|
+
this.debugLogger = debugLogger;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Record tool failure
|
|
66
|
+
*/
|
|
67
|
+
recordFailure(toolName: string, error: string): void {
|
|
68
|
+
if (!this.toolFailures.has(toolName)) {
|
|
69
|
+
this.toolFailures.set(toolName, {
|
|
70
|
+
error,
|
|
71
|
+
since: new Date().toISOString(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Record tool success (clears failure)
|
|
78
|
+
*/
|
|
79
|
+
recordSuccess(toolName: string): void {
|
|
80
|
+
this.toolFailures.delete(toolName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get known issues
|
|
85
|
+
*/
|
|
86
|
+
getIssues(): { tool: string; error: string; since: string }[] {
|
|
87
|
+
return Array.from(this.toolFailures.entries()).map(([tool, data]) => ({
|
|
88
|
+
tool,
|
|
89
|
+
error: data.error,
|
|
90
|
+
since: data.since,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Build status report
|
|
96
|
+
*/
|
|
97
|
+
buildReport(
|
|
98
|
+
connection: ConnectionStatus,
|
|
99
|
+
config: PluginConfig,
|
|
100
|
+
stats: MemoryStats,
|
|
101
|
+
toolGroups: Record<string, string[]>
|
|
102
|
+
): StatusReport {
|
|
103
|
+
const recentCalls = this.debugLogger
|
|
104
|
+
? this.debugLogger.getRecentLogs(10)
|
|
105
|
+
: [];
|
|
106
|
+
|
|
107
|
+
const tools: Record<string, ToolStatus> = {};
|
|
108
|
+
|
|
109
|
+
for (const [group, toolNames] of Object.entries(toolGroups)) {
|
|
110
|
+
const toolStatuses = toolNames.map(name => {
|
|
111
|
+
const logs = this.debugLogger?.getToolLogs(name, 1) || [];
|
|
112
|
+
const lastLog = logs[0];
|
|
113
|
+
const failure = this.toolFailures.get(name);
|
|
114
|
+
|
|
115
|
+
let status: "working" | "error" | "unknown" = "unknown";
|
|
116
|
+
let error: string | undefined;
|
|
117
|
+
let lastSuccess: string | undefined;
|
|
118
|
+
let lastError: string | undefined;
|
|
119
|
+
|
|
120
|
+
if (lastLog) {
|
|
121
|
+
status = lastLog.status === "success" ? "working" : "error";
|
|
122
|
+
if (lastLog.status === "success") {
|
|
123
|
+
lastSuccess = lastLog.timestamp;
|
|
124
|
+
} else {
|
|
125
|
+
lastError = lastLog.timestamp;
|
|
126
|
+
error = lastLog.error;
|
|
127
|
+
}
|
|
128
|
+
} else if (failure) {
|
|
129
|
+
status = "error";
|
|
130
|
+
error = failure.error;
|
|
131
|
+
lastError = failure.since;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
name,
|
|
136
|
+
status,
|
|
137
|
+
error,
|
|
138
|
+
lastSuccess,
|
|
139
|
+
lastError,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const available = toolStatuses.filter(t => t.status === "working").length;
|
|
144
|
+
const failed = toolStatuses.filter(t => t.status === "error").length;
|
|
145
|
+
|
|
146
|
+
tools[group] = {
|
|
147
|
+
enabled: toolNames.length,
|
|
148
|
+
available,
|
|
149
|
+
failed,
|
|
150
|
+
tools: toolStatuses,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
connection,
|
|
156
|
+
config,
|
|
157
|
+
stats,
|
|
158
|
+
tools,
|
|
159
|
+
recentCalls,
|
|
160
|
+
issues: this.getIssues(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Format status report for CLI display
|
|
166
|
+
*/
|
|
167
|
+
static formatReport(report: StatusReport): string {
|
|
168
|
+
const lines: string[] = [];
|
|
169
|
+
|
|
170
|
+
// Header
|
|
171
|
+
lines.push("");
|
|
172
|
+
lines.push("MemoryRelay Plugin Status");
|
|
173
|
+
lines.push("━".repeat(50));
|
|
174
|
+
lines.push("");
|
|
175
|
+
|
|
176
|
+
// Connection
|
|
177
|
+
lines.push("CONNECTION");
|
|
178
|
+
const connSymbol = report.connection.status === "connected" ? "✓" : "✗";
|
|
179
|
+
lines.push(` Status: ${connSymbol} ${report.connection.status}`);
|
|
180
|
+
lines.push(` Endpoint: ${report.connection.endpoint}`);
|
|
181
|
+
lines.push(` Response Time: ${report.connection.responseTime}ms`);
|
|
182
|
+
lines.push(` Last Check: ${new Date(report.connection.lastCheck).toLocaleString()}`);
|
|
183
|
+
lines.push("");
|
|
184
|
+
|
|
185
|
+
// Configuration
|
|
186
|
+
lines.push("CONFIGURATION");
|
|
187
|
+
lines.push(` Agent ID: ${report.config.agentId}`);
|
|
188
|
+
const recallStatus = report.config.autoRecall
|
|
189
|
+
? `✓ Enabled (limit: ${report.config.recallLimit}, threshold: ${report.config.recallThreshold})`
|
|
190
|
+
: "✗ Disabled";
|
|
191
|
+
lines.push(` Auto-Recall: ${recallStatus}`);
|
|
192
|
+
lines.push(` Auto-Capture: ${report.config.autoCapture ? "✓ Enabled" : "✗ Disabled"}`);
|
|
193
|
+
if (report.config.defaultProject) {
|
|
194
|
+
lines.push(` Default Project: ${report.config.defaultProject}`);
|
|
195
|
+
}
|
|
196
|
+
lines.push("");
|
|
197
|
+
|
|
198
|
+
// Memory Statistics
|
|
199
|
+
lines.push("MEMORY STATISTICS");
|
|
200
|
+
lines.push(` Total Memories: ${report.stats.total_memories}`);
|
|
201
|
+
if (report.stats.memories_today !== undefined) {
|
|
202
|
+
lines.push(` Today: ${report.stats.memories_today}`);
|
|
203
|
+
}
|
|
204
|
+
if (report.stats.last_stored) {
|
|
205
|
+
const lastStored = new Date(report.stats.last_stored);
|
|
206
|
+
const ago = this.formatTimeAgo(lastStored);
|
|
207
|
+
lines.push(` Last Stored: ${ago}`);
|
|
208
|
+
}
|
|
209
|
+
if (report.stats.search_count_24h !== undefined) {
|
|
210
|
+
lines.push(` Searches (24h): ${report.stats.search_count_24h}`);
|
|
211
|
+
}
|
|
212
|
+
lines.push("");
|
|
213
|
+
|
|
214
|
+
// Tools Status
|
|
215
|
+
const totalEnabled = Object.values(report.tools).reduce((sum, g) => sum + g.enabled, 0);
|
|
216
|
+
const totalAvailable = Object.values(report.tools).reduce((sum, g) => sum + g.available, 0);
|
|
217
|
+
lines.push(`TOOLS STATUS (${totalAvailable}/${totalEnabled} working)`);
|
|
218
|
+
|
|
219
|
+
for (const [groupName, group] of Object.entries(report.tools)) {
|
|
220
|
+
const symbol = group.failed === 0 ? "✓" : group.failed === group.enabled ? "✗" : "⚠";
|
|
221
|
+
const label = groupName.charAt(0).toUpperCase() + groupName.slice(1);
|
|
222
|
+
lines.push(` ${symbol} ${label}: ${group.available}/${group.enabled} working`);
|
|
223
|
+
|
|
224
|
+
// Show failed tools
|
|
225
|
+
const failedTools = group.tools.filter(t => t.status === "error");
|
|
226
|
+
for (const tool of failedTools) {
|
|
227
|
+
lines.push(` ✗ ${tool.name} (${tool.error})`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
lines.push("");
|
|
231
|
+
|
|
232
|
+
// Recent Activity
|
|
233
|
+
if (report.recentCalls.length > 0) {
|
|
234
|
+
lines.push(`RECENT ACTIVITY (last ${report.recentCalls.length} calls)`);
|
|
235
|
+
for (const call of report.recentCalls.reverse()) {
|
|
236
|
+
const time = new Date(call.timestamp).toLocaleTimeString();
|
|
237
|
+
const status = call.status === "success" ? "✓" : "✗";
|
|
238
|
+
const duration = `${call.duration}ms`;
|
|
239
|
+
lines.push(` ${time} ${call.tool.padEnd(18)} ${duration.padStart(6)} ${status}`);
|
|
240
|
+
}
|
|
241
|
+
lines.push("");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Known Issues
|
|
245
|
+
if (report.issues.length > 0) {
|
|
246
|
+
lines.push(`KNOWN ISSUES (${report.issues.length})`);
|
|
247
|
+
for (const issue of report.issues) {
|
|
248
|
+
const since = this.formatTimeAgo(new Date(issue.since));
|
|
249
|
+
lines.push(` ⚠ ${issue.tool} - ${issue.error} (since ${since})`);
|
|
250
|
+
}
|
|
251
|
+
lines.push("");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Footer
|
|
255
|
+
lines.push("For detailed logs, run: openclaw memoryrelay logs");
|
|
256
|
+
lines.push("For troubleshooting: https://github.com/MemoryRelay/api/issues/213");
|
|
257
|
+
lines.push("");
|
|
258
|
+
|
|
259
|
+
return lines.join("\n");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Format time ago string
|
|
264
|
+
*/
|
|
265
|
+
private static formatTimeAgo(date: Date): string {
|
|
266
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
267
|
+
|
|
268
|
+
if (seconds < 60) return `${seconds} seconds ago`;
|
|
269
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
|
|
270
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
|
|
271
|
+
return `${Math.floor(seconds / 86400)} days ago`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Format compact status (for inline display)
|
|
276
|
+
*/
|
|
277
|
+
static formatCompact(report: StatusReport): string {
|
|
278
|
+
const totalEnabled = Object.values(report.tools).reduce((sum, g) => sum + g.enabled, 0);
|
|
279
|
+
const totalAvailable = Object.values(report.tools).reduce((sum, g) => sum + g.available, 0);
|
|
280
|
+
const symbol = report.connection.status === "connected" ? "✓" : "✗";
|
|
281
|
+
|
|
282
|
+
return `MemoryRelay: ${symbol} ${report.connection.status}, ${totalAvailable}/${totalEnabled} tools working`;
|
|
283
|
+
}
|
|
284
|
+
}
|