llm-deep-trace 0.1.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/LICENSE +21 -0
- package/README.md +159 -0
- package/bin/llm-deep-trace.js +24 -0
- package/next.config.ts +8 -0
- package/package.json +56 -0
- package/postcss.config.mjs +5 -0
- package/public/banner-v2.png +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.png +0 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agent-config/route.ts +31 -0
- package/src/app/api/all-sessions/route.ts +9 -0
- package/src/app/api/analytics/route.ts +379 -0
- package/src/app/api/detect-agents/route.ts +170 -0
- package/src/app/api/image/route.ts +73 -0
- package/src/app/api/search/route.ts +28 -0
- package/src/app/api/session-by-key/route.ts +21 -0
- package/src/app/api/sessions/[sessionId]/messages/route.ts +46 -0
- package/src/app/api/sse/route.ts +86 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +3518 -0
- package/src/app/icon.svg +4 -0
- package/src/app/layout.tsx +20 -0
- package/src/app/page.tsx +5 -0
- package/src/components/AnalyticsDashboard.tsx +393 -0
- package/src/components/App.tsx +243 -0
- package/src/components/CopyButton.tsx +42 -0
- package/src/components/Logo.tsx +20 -0
- package/src/components/MainPanel.tsx +1128 -0
- package/src/components/MessageRenderer.tsx +983 -0
- package/src/components/SessionTree.tsx +505 -0
- package/src/components/SettingsPanel.tsx +160 -0
- package/src/components/SetupView.tsx +206 -0
- package/src/components/Sidebar.tsx +714 -0
- package/src/components/ThemeToggle.tsx +54 -0
- package/src/lib/client-utils.ts +360 -0
- package/src/lib/normalizers.ts +371 -0
- package/src/lib/sessions.ts +1223 -0
- package/src/lib/store.ts +518 -0
- package/src/lib/types.ts +112 -0
- package/src/lib/useSSE.ts +81 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getSessionMessages } from "@/lib/sessions";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
// Cap any single string value at this length before sending to the browser.
|
|
7
|
+
// Large tool results (file reads, bash output) are the main offender.
|
|
8
|
+
const MAX_CHARS = 6000;
|
|
9
|
+
|
|
10
|
+
function truncateDeep(value: unknown, path = ""): unknown {
|
|
11
|
+
if (typeof value === "string") {
|
|
12
|
+
if (value.length > MAX_CHARS) {
|
|
13
|
+
return value.slice(0, MAX_CHARS) + `\n\n… [truncated — ${Math.round(value.length / 1024)}KB total]`;
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return value.map((v) => truncateDeep(v));
|
|
19
|
+
}
|
|
20
|
+
if (value !== null && typeof value === "object") {
|
|
21
|
+
const out: Record<string, unknown> = {};
|
|
22
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
23
|
+
out[k] = truncateDeep(v, path ? `${path}.${k}` : k);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function GET(
|
|
31
|
+
_request: Request,
|
|
32
|
+
{ params }: { params: Promise<{ sessionId: string }> }
|
|
33
|
+
) {
|
|
34
|
+
const { sessionId } = await params;
|
|
35
|
+
const url = new URL(_request.url);
|
|
36
|
+
const source = url.searchParams.get("source") || "kova";
|
|
37
|
+
const full = url.searchParams.get("full") === "1"; // opt-in to untruncated
|
|
38
|
+
|
|
39
|
+
const entries = getSessionMessages(sessionId, source);
|
|
40
|
+
if (!entries) {
|
|
41
|
+
return NextResponse.json({ error: "Session not found" }, { status: 404 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result = full ? entries : truncateDeep(entries);
|
|
45
|
+
return NextResponse.json(result);
|
|
46
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
export const runtime = "nodejs";
|
|
7
|
+
|
|
8
|
+
const SESSIONS_DIR = path.join(
|
|
9
|
+
os.homedir(),
|
|
10
|
+
".openclaw",
|
|
11
|
+
"agents",
|
|
12
|
+
"main",
|
|
13
|
+
"sessions"
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export async function GET() {
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
|
|
19
|
+
const stream = new ReadableStream({
|
|
20
|
+
start(controller) {
|
|
21
|
+
function sendEvent(data: Record<string, unknown>) {
|
|
22
|
+
controller.enqueue(
|
|
23
|
+
encoder.encode(`data: ${JSON.stringify(data)}\n\n`)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Send initial connected event
|
|
28
|
+
sendEvent({ event: "connected" });
|
|
29
|
+
|
|
30
|
+
// Set up file watching using fs.watch
|
|
31
|
+
let watcher: fs.FSWatcher | null = null;
|
|
32
|
+
try {
|
|
33
|
+
if (fs.existsSync(SESSIONS_DIR)) {
|
|
34
|
+
watcher = fs.watch(SESSIONS_DIR, (_eventType, filename) => {
|
|
35
|
+
if (!filename) return;
|
|
36
|
+
try {
|
|
37
|
+
if (filename.includes(".jsonl")) {
|
|
38
|
+
const sessionId = filename.split(".jsonl")[0];
|
|
39
|
+
sendEvent({
|
|
40
|
+
event: "session_updated",
|
|
41
|
+
sessionId,
|
|
42
|
+
});
|
|
43
|
+
} else if (filename === "sessions.json") {
|
|
44
|
+
sendEvent({ event: "sessions_index_updated" });
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// stream may be closed
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// watcher setup failed
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Keep-alive ping every 30s
|
|
56
|
+
const pingInterval = setInterval(() => {
|
|
57
|
+
try {
|
|
58
|
+
sendEvent({ event: "ping" });
|
|
59
|
+
} catch {
|
|
60
|
+
clearInterval(pingInterval);
|
|
61
|
+
}
|
|
62
|
+
}, 30000);
|
|
63
|
+
|
|
64
|
+
// Cleanup on close
|
|
65
|
+
const cleanup = () => {
|
|
66
|
+
clearInterval(pingInterval);
|
|
67
|
+
if (watcher) watcher.close();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Store cleanup for cancel
|
|
71
|
+
(controller as unknown as Record<string, unknown>).__cleanup = cleanup;
|
|
72
|
+
},
|
|
73
|
+
cancel() {
|
|
74
|
+
// Called when the client disconnects
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return new Response(stream, {
|
|
79
|
+
headers: {
|
|
80
|
+
"Content-Type": "text/event-stream",
|
|
81
|
+
"Cache-Control": "no-cache, no-transform",
|
|
82
|
+
Connection: "keep-alive",
|
|
83
|
+
"X-Accel-Buffering": "no",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
Binary file
|