@mandujs/core 0.20.8 → 0.20.9
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/package.json +1 -1
- package/src/config/mandu.ts +2 -0
- package/src/kitchen/kitchen-handler.ts +81 -4
- package/src/kitchen/kitchen-ui.ts +1311 -906
- package/src/observability/index.ts +10 -0
- package/src/observability/sqlite-store.ts +254 -0
- package/src/runtime/server.ts +107 -2
package/package.json
CHANGED
package/src/config/mandu.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { FileAPI } from "./api/file-api";
|
|
|
15
15
|
import { GuardDecisionManager } from "./api/guard-decisions";
|
|
16
16
|
import { ContractPlaygroundAPI } from "./api/contract-api";
|
|
17
17
|
import { renderKitchenHTML } from "./kitchen-ui";
|
|
18
|
+
import { eventBus } from "../observability/event-bus";
|
|
18
19
|
import fs from "fs/promises";
|
|
19
20
|
import path from "path";
|
|
20
21
|
|
|
@@ -77,6 +78,21 @@ export function getRecentRequests(): RequestEntry[] {
|
|
|
77
78
|
return [...recentRequests].reverse();
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/** Parse a window string like "5m", "30s", "1h" into milliseconds. */
|
|
82
|
+
function parseWindow(input: string): number {
|
|
83
|
+
const match = /^(\d+)\s*(ms|s|m|h)?$/.exec(input.trim());
|
|
84
|
+
if (!match) return 5 * 60 * 1000;
|
|
85
|
+
const value = parseInt(match[1], 10);
|
|
86
|
+
const unit = match[2] || "m";
|
|
87
|
+
switch (unit) {
|
|
88
|
+
case "ms": return value;
|
|
89
|
+
case "s": return value * 1000;
|
|
90
|
+
case "m": return value * 60 * 1000;
|
|
91
|
+
case "h": return value * 60 * 60 * 1000;
|
|
92
|
+
default: return 5 * 60 * 1000;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
export class KitchenHandler {
|
|
81
97
|
private sse: ActivitySSEBroadcaster;
|
|
82
98
|
private guardAPI: GuardAPI;
|
|
@@ -222,19 +238,41 @@ export class KitchenHandler {
|
|
|
222
238
|
return Response.json({ removed: true });
|
|
223
239
|
}
|
|
224
240
|
|
|
225
|
-
// Requests API — recent HTTP
|
|
241
|
+
// Requests API — recent HTTP events from eventBus (fallback: ring buffer)
|
|
226
242
|
if (sub === "/api/requests" && req.method === "GET") {
|
|
227
|
-
|
|
243
|
+
const url = new URL(req.url);
|
|
244
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "100", 10) || 100, 500);
|
|
245
|
+
const busEvents = eventBus.getRecent(limit, { type: "http" });
|
|
246
|
+
if (busEvents.length > 0) {
|
|
247
|
+
return Response.json({ requests: busEvents.slice().reverse() });
|
|
248
|
+
}
|
|
249
|
+
return Response.json({ requests: getRecentRequests().slice(0, limit) });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Correlation API — all events linked to a correlationId
|
|
253
|
+
if (sub === "/api/correlation" && req.method === "GET") {
|
|
254
|
+
const url = new URL(req.url);
|
|
255
|
+
const cid = url.searchParams.get("id") || "";
|
|
256
|
+
if (!cid) return Response.json({ events: [] });
|
|
257
|
+
const all = eventBus.getRecent(500);
|
|
258
|
+
const events = all.filter((e) => e.correlationId === cid);
|
|
259
|
+
return Response.json({ events });
|
|
228
260
|
}
|
|
229
261
|
|
|
230
|
-
// Activity API — recent MCP
|
|
262
|
+
// Activity API — recent MCP events from eventBus (fallback: activity.jsonl)
|
|
231
263
|
if (sub === "/api/activity" && req.method === "GET") {
|
|
264
|
+
const url = new URL(req.url);
|
|
265
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "100", 10) || 100, 500);
|
|
266
|
+
const busEvents = eventBus.getRecent(limit, { type: "mcp" });
|
|
267
|
+
if (busEvents.length > 0) {
|
|
268
|
+
return Response.json({ events: busEvents.slice().reverse() });
|
|
269
|
+
}
|
|
232
270
|
const events = await this.readRecentActivity();
|
|
233
271
|
return Response.json({ events });
|
|
234
272
|
}
|
|
235
273
|
|
|
236
274
|
// Cache API — cache store stats
|
|
237
|
-
if (sub === "/api/cache" && req.method === "GET") {
|
|
275
|
+
if ((sub === "/api/cache" || sub === "/api/cache-stats") && req.method === "GET") {
|
|
238
276
|
const store = getGlobalCache();
|
|
239
277
|
return Response.json({
|
|
240
278
|
enabled: !!store,
|
|
@@ -243,6 +281,45 @@ export class KitchenHandler {
|
|
|
243
281
|
});
|
|
244
282
|
}
|
|
245
283
|
|
|
284
|
+
// Metrics API — rolling-window eventBus stats
|
|
285
|
+
if (sub === "/api/metrics" && req.method === "GET") {
|
|
286
|
+
const url = new URL(req.url);
|
|
287
|
+
const windowParam = url.searchParams.get("window") || "5m";
|
|
288
|
+
const windowMs = parseWindow(windowParam);
|
|
289
|
+
const stats = eventBus.getStats(windowMs);
|
|
290
|
+
const httpEvents = eventBus.getRecent(500, { type: "http" })
|
|
291
|
+
.filter((e) => e.timestamp >= Date.now() - windowMs);
|
|
292
|
+
const durations = httpEvents
|
|
293
|
+
.map((e) => e.duration)
|
|
294
|
+
.filter((d): d is number => typeof d === "number")
|
|
295
|
+
.sort((a, b) => a - b);
|
|
296
|
+
const percentile = (p: number) => {
|
|
297
|
+
if (!durations.length) return 0;
|
|
298
|
+
const idx = Math.min(durations.length - 1, Math.floor((p / 100) * durations.length));
|
|
299
|
+
return durations[idx];
|
|
300
|
+
};
|
|
301
|
+
const totalEvents = Object.values(stats).reduce((sum, s) => sum + s.count, 0);
|
|
302
|
+
const totalErrors = Object.values(stats).reduce((sum, s) => sum + s.errors, 0);
|
|
303
|
+
return Response.json({
|
|
304
|
+
window: windowParam,
|
|
305
|
+
windowMs,
|
|
306
|
+
stats,
|
|
307
|
+
http: {
|
|
308
|
+
count: httpEvents.length,
|
|
309
|
+
p50: percentile(50),
|
|
310
|
+
p95: percentile(95),
|
|
311
|
+
p99: percentile(99),
|
|
312
|
+
},
|
|
313
|
+
mcp: {
|
|
314
|
+
count: stats.mcp.count,
|
|
315
|
+
errors: stats.mcp.errors,
|
|
316
|
+
avgDuration: stats.mcp.avgDuration,
|
|
317
|
+
},
|
|
318
|
+
errorRate: totalEvents > 0 ? totalErrors / totalEvents : 0,
|
|
319
|
+
totalEvents,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
246
323
|
// Error API (Kitchen → MCP bridge)
|
|
247
324
|
if (sub === "/api/errors" && req.method === "POST") {
|
|
248
325
|
try {
|