forge-openclaw-plugin 0.2.20 → 0.2.21
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/dist/assets/{board-DGbXWEuu.js → board-_C6oMy5w.js} +2 -2
- package/dist/assets/{board-DGbXWEuu.js.map → board-_C6oMy5w.js.map} +1 -1
- package/dist/assets/index-B4A6TooJ.js +63 -0
- package/dist/assets/index-B4A6TooJ.js.map +1 -0
- package/dist/assets/index-D6Xs_2mo.css +1 -0
- package/dist/assets/{motion-B5Qoz2Ci.js → motion-D4sZgCHd.js} +2 -2
- package/dist/assets/{motion-B5Qoz2Ci.js.map → motion-D4sZgCHd.js.map} +1 -1
- package/dist/assets/{table-D_iurDQu.js → table-BWzTaky1.js} +2 -2
- package/dist/assets/{table-D_iurDQu.js.map → table-BWzTaky1.js.map} +1 -1
- package/dist/assets/{ui-D5QUYUq4.js → ui-BzK4azQb.js} +2 -2
- package/dist/assets/{ui-D5QUYUq4.js.map → ui-BzK4azQb.js.map} +1 -1
- package/dist/assets/vendor-De38P6YR.js +729 -0
- package/dist/assets/vendor-De38P6YR.js.map +1 -0
- package/dist/assets/{viz-BD9WSxHz.js → viz-C6hfyqzu.js} +2 -2
- package/dist/assets/{viz-BD9WSxHz.js.map → viz-C6hfyqzu.js.map} +1 -1
- package/dist/index.html +8 -8
- package/dist/server/app.js +301 -19
- package/dist/server/health.js +82 -21
- package/dist/server/managers/platform/background-job-manager.js +103 -8
- package/dist/server/managers/platform/llm-manager.js +91 -5
- package/dist/server/managers/platform/openai-responses-provider.js +683 -70
- package/dist/server/repositories/diagnostic-logs.js +243 -0
- package/dist/server/repositories/wiki-memory.js +595 -62
- package/dist/server/types.js +56 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/023_diagnostic_logs.sql +28 -0
- package/skills/forge-openclaw/SKILL.md +14 -0
- package/dist/assets/index-4-1WI9i7.css +0 -1
- package/dist/assets/index-BZbHajNK.js +0 -63
- package/dist/assets/index-BZbHajNK.js.map +0 -1
- package/dist/assets/vendor-KARp8LAR.js +0 -706
- package/dist/assets/vendor-KARp8LAR.js.map +0 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { getDatabase } from "../db.js";
|
|
4
|
+
import { createDiagnosticLogSchema, diagnosticLogEntrySchema } from "../types.js";
|
|
5
|
+
const MAX_LOG_ENTRIES = 5_000;
|
|
6
|
+
export const DIAGNOSTIC_LOG_RETENTION_DAYS = 14;
|
|
7
|
+
export const DIAGNOSTIC_LOG_RETENTION_SWEEP_INTERVAL_MS = 15 * 60 * 1000;
|
|
8
|
+
const MAX_STRING_LENGTH = 4_000;
|
|
9
|
+
const MAX_ARRAY_ITEMS = 24;
|
|
10
|
+
const MAX_OBJECT_KEYS = 40;
|
|
11
|
+
const MAX_DEPTH = 4;
|
|
12
|
+
let nextRetentionSweepAt = 0;
|
|
13
|
+
function nowIso() {
|
|
14
|
+
return new Date().toISOString();
|
|
15
|
+
}
|
|
16
|
+
function sanitizeDiagnosticValue(value, depth = 0) {
|
|
17
|
+
if (value === null ||
|
|
18
|
+
typeof value === "boolean" ||
|
|
19
|
+
typeof value === "number") {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
return value.length > MAX_STRING_LENGTH
|
|
24
|
+
? `${value.slice(0, MAX_STRING_LENGTH)}…`
|
|
25
|
+
: value;
|
|
26
|
+
}
|
|
27
|
+
if (typeof value === "bigint") {
|
|
28
|
+
return value.toString();
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
31
|
+
return String(value);
|
|
32
|
+
}
|
|
33
|
+
if (value instanceof Date) {
|
|
34
|
+
return value.toISOString();
|
|
35
|
+
}
|
|
36
|
+
if (value instanceof Error) {
|
|
37
|
+
return {
|
|
38
|
+
name: value.name,
|
|
39
|
+
message: value.message,
|
|
40
|
+
stack: typeof value.stack === "string"
|
|
41
|
+
? sanitizeDiagnosticValue(value.stack, depth + 1)
|
|
42
|
+
: null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (Buffer.isBuffer(value)) {
|
|
46
|
+
return {
|
|
47
|
+
type: "Buffer",
|
|
48
|
+
bytes: value.byteLength
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (depth >= MAX_DEPTH) {
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return `[Array(${value.length})]`;
|
|
54
|
+
}
|
|
55
|
+
return "[Object]";
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
return value
|
|
59
|
+
.slice(0, MAX_ARRAY_ITEMS)
|
|
60
|
+
.map((entry) => sanitizeDiagnosticValue(entry, depth + 1));
|
|
61
|
+
}
|
|
62
|
+
if (value && typeof value === "object") {
|
|
63
|
+
const entries = Object.entries(value).slice(0, MAX_OBJECT_KEYS);
|
|
64
|
+
return Object.fromEntries(entries.map(([key, entry]) => [
|
|
65
|
+
key,
|
|
66
|
+
sanitizeDiagnosticValue(entry, depth + 1)
|
|
67
|
+
]));
|
|
68
|
+
}
|
|
69
|
+
return String(value);
|
|
70
|
+
}
|
|
71
|
+
function sanitizeDetails(details) {
|
|
72
|
+
if (!details) {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
return Object.fromEntries(Object.entries(details).map(([key, value]) => [
|
|
76
|
+
key,
|
|
77
|
+
sanitizeDiagnosticValue(value)
|
|
78
|
+
]));
|
|
79
|
+
}
|
|
80
|
+
function mapRow(row) {
|
|
81
|
+
return diagnosticLogEntrySchema.parse({
|
|
82
|
+
id: row.id,
|
|
83
|
+
level: row.level,
|
|
84
|
+
source: row.source,
|
|
85
|
+
scope: row.scope,
|
|
86
|
+
eventKey: row.event_key,
|
|
87
|
+
message: row.message,
|
|
88
|
+
route: row.route,
|
|
89
|
+
functionName: row.function_name,
|
|
90
|
+
requestId: row.request_id,
|
|
91
|
+
entityType: row.entity_type,
|
|
92
|
+
entityId: row.entity_id,
|
|
93
|
+
jobId: row.job_id,
|
|
94
|
+
details: JSON.parse(row.details_json),
|
|
95
|
+
createdAt: row.created_at
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function pruneDiagnosticLogs() {
|
|
99
|
+
const expiredBefore = new Date(Date.now() - DIAGNOSTIC_LOG_RETENTION_DAYS * 24 * 60 * 60 * 1_000).toISOString();
|
|
100
|
+
const expiredResult = getDatabase()
|
|
101
|
+
.prepare(`DELETE FROM diagnostic_logs
|
|
102
|
+
WHERE created_at < ?`)
|
|
103
|
+
.run(expiredBefore);
|
|
104
|
+
const overflowResult = getDatabase().prepare(`DELETE FROM diagnostic_logs
|
|
105
|
+
WHERE id IN (
|
|
106
|
+
SELECT id
|
|
107
|
+
FROM diagnostic_logs
|
|
108
|
+
ORDER BY created_at DESC
|
|
109
|
+
LIMIT -1 OFFSET ?
|
|
110
|
+
)`).run(MAX_LOG_ENTRIES);
|
|
111
|
+
return (expiredResult.changes ?? 0) + (overflowResult.changes ?? 0);
|
|
112
|
+
}
|
|
113
|
+
function checkpointDiagnosticLogStore() {
|
|
114
|
+
getDatabase().exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
|
115
|
+
}
|
|
116
|
+
export function enforceDiagnosticLogRetention(options = {}) {
|
|
117
|
+
const now = options.now ?? new Date();
|
|
118
|
+
const startedAt = now.getTime();
|
|
119
|
+
if (!options.force && startedAt < nextRetentionSweepAt) {
|
|
120
|
+
return { prunedCount: 0, ran: false };
|
|
121
|
+
}
|
|
122
|
+
nextRetentionSweepAt =
|
|
123
|
+
startedAt + DIAGNOSTIC_LOG_RETENTION_SWEEP_INTERVAL_MS;
|
|
124
|
+
const prunedCount = pruneDiagnosticLogs();
|
|
125
|
+
if (prunedCount > 0) {
|
|
126
|
+
checkpointDiagnosticLogStore();
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
prunedCount,
|
|
130
|
+
ran: true
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function recordDiagnosticLog(input, now = new Date()) {
|
|
134
|
+
const parsed = createDiagnosticLogSchema.parse(input);
|
|
135
|
+
const entry = diagnosticLogEntrySchema.parse({
|
|
136
|
+
id: `diag_${randomUUID().replaceAll("-", "").slice(0, 10)}`,
|
|
137
|
+
level: parsed.level,
|
|
138
|
+
source: parsed.source ?? "server",
|
|
139
|
+
scope: parsed.scope,
|
|
140
|
+
eventKey: parsed.eventKey,
|
|
141
|
+
message: parsed.message,
|
|
142
|
+
route: parsed.route ?? null,
|
|
143
|
+
functionName: parsed.functionName ?? null,
|
|
144
|
+
requestId: parsed.requestId ?? null,
|
|
145
|
+
entityType: parsed.entityType ?? null,
|
|
146
|
+
entityId: parsed.entityId ?? null,
|
|
147
|
+
jobId: parsed.jobId ?? null,
|
|
148
|
+
details: sanitizeDetails(parsed.details),
|
|
149
|
+
createdAt: now.toISOString()
|
|
150
|
+
});
|
|
151
|
+
getDatabase().prepare(`INSERT INTO diagnostic_logs (
|
|
152
|
+
id, level, source, scope, event_key, message, route, function_name,
|
|
153
|
+
request_id, entity_type, entity_id, job_id, details_json, created_at
|
|
154
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(entry.id, entry.level, entry.source, entry.scope, entry.eventKey, entry.message, entry.route, entry.functionName, entry.requestId, entry.entityType, entry.entityId, entry.jobId, JSON.stringify(entry.details), entry.createdAt);
|
|
155
|
+
enforceDiagnosticLogRetention({ now });
|
|
156
|
+
return entry;
|
|
157
|
+
}
|
|
158
|
+
export function listDiagnosticLogs(filters = {}) {
|
|
159
|
+
enforceDiagnosticLogRetention();
|
|
160
|
+
const whereClauses = [];
|
|
161
|
+
const params = [];
|
|
162
|
+
if (filters.level) {
|
|
163
|
+
whereClauses.push("level = ?");
|
|
164
|
+
params.push(filters.level);
|
|
165
|
+
}
|
|
166
|
+
if (filters.source) {
|
|
167
|
+
whereClauses.push("source = ?");
|
|
168
|
+
params.push(filters.source);
|
|
169
|
+
}
|
|
170
|
+
if (filters.scope) {
|
|
171
|
+
whereClauses.push("scope = ?");
|
|
172
|
+
params.push(filters.scope);
|
|
173
|
+
}
|
|
174
|
+
if (filters.route) {
|
|
175
|
+
whereClauses.push("route = ?");
|
|
176
|
+
params.push(filters.route);
|
|
177
|
+
}
|
|
178
|
+
if (filters.entityType) {
|
|
179
|
+
whereClauses.push("entity_type = ?");
|
|
180
|
+
params.push(filters.entityType);
|
|
181
|
+
}
|
|
182
|
+
if (filters.entityId) {
|
|
183
|
+
whereClauses.push("entity_id = ?");
|
|
184
|
+
params.push(filters.entityId);
|
|
185
|
+
}
|
|
186
|
+
if (filters.jobId) {
|
|
187
|
+
whereClauses.push("job_id = ?");
|
|
188
|
+
params.push(filters.jobId);
|
|
189
|
+
}
|
|
190
|
+
if (filters.search) {
|
|
191
|
+
whereClauses.push("(message LIKE ? OR scope LIKE ? OR event_key LIKE ? OR IFNULL(route, '') LIKE ? OR details_json LIKE ?)");
|
|
192
|
+
const term = `%${filters.search}%`;
|
|
193
|
+
params.push(term, term, term, term, term);
|
|
194
|
+
}
|
|
195
|
+
if (filters.beforeCreatedAt && filters.beforeId) {
|
|
196
|
+
whereClauses.push("(created_at < ? OR (created_at = ? AND id < ?))");
|
|
197
|
+
params.push(filters.beforeCreatedAt, filters.beforeCreatedAt, filters.beforeId);
|
|
198
|
+
}
|
|
199
|
+
const whereSql = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
200
|
+
const limit = filters.limit ?? 200;
|
|
201
|
+
const rows = getDatabase()
|
|
202
|
+
.prepare(`SELECT id, level, source, scope, event_key, message, route, function_name,
|
|
203
|
+
request_id, entity_type, entity_id, job_id, details_json, created_at
|
|
204
|
+
FROM diagnostic_logs
|
|
205
|
+
${whereSql}
|
|
206
|
+
ORDER BY created_at DESC, id DESC
|
|
207
|
+
LIMIT ?`)
|
|
208
|
+
.all(...params, limit);
|
|
209
|
+
const logs = rows.map(mapRow);
|
|
210
|
+
const tail = rows.at(-1) ?? null;
|
|
211
|
+
return {
|
|
212
|
+
logs,
|
|
213
|
+
nextCursor: rows.length >= limit && tail
|
|
214
|
+
? {
|
|
215
|
+
beforeCreatedAt: tail.created_at,
|
|
216
|
+
beforeId: tail.id
|
|
217
|
+
}
|
|
218
|
+
: null
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export function normalizeDiagnosticSource(value) {
|
|
222
|
+
return value === "ui" ||
|
|
223
|
+
value === "openclaw" ||
|
|
224
|
+
value === "agent" ||
|
|
225
|
+
value === "system" ||
|
|
226
|
+
value === "server"
|
|
227
|
+
? value
|
|
228
|
+
: "server";
|
|
229
|
+
}
|
|
230
|
+
export function serializeDiagnosticError(error) {
|
|
231
|
+
return sanitizeDiagnosticValue(error);
|
|
232
|
+
}
|
|
233
|
+
export function createDiagnosticMessage(input) {
|
|
234
|
+
const method = input.method?.toUpperCase() || "CALL";
|
|
235
|
+
const route = input.route || "unknown-route";
|
|
236
|
+
if (typeof input.statusCode === "number") {
|
|
237
|
+
return `${method} ${route} -> ${input.statusCode}`;
|
|
238
|
+
}
|
|
239
|
+
return `${method} ${route}`;
|
|
240
|
+
}
|
|
241
|
+
export function createDiagnosticTimestamp() {
|
|
242
|
+
return nowIso();
|
|
243
|
+
}
|