Skip to content

Commit bdad596

Browse files
shreyas-lyzrclaude
andcommitted
feat: deepen log visibility — global error handlers, HTTP/WS instrumentation
- Adds process-level uncaughtException, unhandledRejection, and warning handlers - Logs all HTTP API requests with method, path, status, and duration - jsonReply auto-logs any 400+ response with error detail - Adds HTTP server error and clientError handlers - Adds WebSocket server and per-connection error handlers - Logs unauthorized WS rejections with remote IP - WS message handler now logs errors with stack instead of swallowing - Error objects formatted with full stack traces via formatArg - Startup banner shows agent dir, model, Composio, Telegram, and auth status Bumps version to 1.4.1. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b8185ca commit bdad596

2 files changed

Lines changed: 76 additions & 5 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gitclaw",
3-
"version": "1.4.0",
3+
"version": "1.4.1",
44
"description": "A universal git-native multimodal always learning AI Agent (TinyHuman)",
55
"author": "shreyaskapale",
66
"license": "MIT",

src/voice/server.ts

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ function extractSource(msg: string): { source: string; cleaned: string } {
5656
return { source: "system", cleaned: msg };
5757
}
5858

59+
function formatArg(a: any): string {
60+
if (a == null) return String(a);
61+
if (typeof a === "string") return a;
62+
if (a instanceof Error) return `${a.message}${a.stack ? "\n" + a.stack : ""}`;
63+
try { return JSON.stringify(a, (_k, v) => v instanceof Error ? { message: v.message, stack: v.stack } : v); }
64+
catch { return String(a); }
65+
}
66+
67+
export function logToBuffer(source: string, level: "info" | "warn" | "error", message: string): LogEntry {
68+
const entry = logBuffer.push(source, level, message);
69+
if (logBroadcast) logBroadcast(entry);
70+
return entry;
71+
}
72+
5973
function installConsoleIntercept() {
6074
const origLog = console.log.bind(console);
6175
const origError = console.error.bind(console);
@@ -64,7 +78,7 @@ function installConsoleIntercept() {
6478
function intercept(level: "info" | "warn" | "error", origFn: (...args: any[]) => void, ...args: any[]) {
6579
origFn(...args);
6680
try {
67-
const raw = args.map(a => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
81+
const raw = args.map(formatArg).join(" ");
6882
const clean = stripAnsi(raw);
6983
if (!clean.trim()) return;
7084
const { source, cleaned } = extractSource(clean);
@@ -80,6 +94,21 @@ function installConsoleIntercept() {
8094

8195
installConsoleIntercept();
8296

97+
// Global error handlers — capture everything that would otherwise be lost
98+
if (!(process as any).__gitclawLogHandlersInstalled) {
99+
(process as any).__gitclawLogHandlersInstalled = true;
100+
process.on("uncaughtException", (err: Error) => {
101+
console.error(`[system] UNCAUGHT EXCEPTION: ${err.message}\n${err.stack}`);
102+
});
103+
process.on("unhandledRejection", (reason: any) => {
104+
const msg = reason instanceof Error ? `${reason.message}\n${reason.stack}` : String(reason);
105+
console.error(`[system] UNHANDLED REJECTION: ${msg}`);
106+
});
107+
process.on("warning", (warning: Error) => {
108+
console.warn(`[system] Node warning: ${warning.name}: ${warning.message}`);
109+
});
110+
}
111+
83112
// ── Background memory saver ────────────────────────────────────────────
84113
// Patterns that indicate the user is sharing personal info worth saving.
85114
// This runs server-side so we don't depend on the voice LLM deciding to save.
@@ -1680,6 +1709,11 @@ ${runningContext}`;
16801709
}
16811710

16821711
function jsonReply(res: ServerResponse, status: number, data: any) {
1712+
if (status >= 500 && data && data.error) {
1713+
console.error(`[http] 500 response: ${data.error}`);
1714+
} else if (status >= 400 && data && data.error) {
1715+
console.warn(`[http] ${status} response: ${data.error}`);
1716+
}
16831717
res.writeHead(status, { "Content-Type": "application/json" });
16841718
res.end(JSON.stringify(data));
16851719
}
@@ -1744,6 +1778,7 @@ return false;
17441778

17451779
// HTTP server
17461780
const httpServer: Server = createServer(async (req, res) => {
1781+
const reqStart = Date.now();
17471782
res.setHeader("Access-Control-Allow-Origin", "*");
17481783
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
17491784
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
@@ -1755,6 +1790,21 @@ return false;
17551790

17561791
const url = new URL(req.url || "/", `http://localhost:${port}`);
17571792

1793+
// Log every HTTP request (skip UI + static paths to reduce noise; always log API/errors)
1794+
const isApi = url.pathname.startsWith("/api/");
1795+
res.on("finish", () => {
1796+
if (isApi || res.statusCode >= 400) {
1797+
const dur = Date.now() - reqStart;
1798+
const level = res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warn" : "log";
1799+
const line = `[http] ${req.method} ${url.pathname}${res.statusCode} (${dur}ms)`;
1800+
if (level === "error") console.error(line);
1801+
else if (level === "warn") console.warn(line);
1802+
else console.log(line);
1803+
}
1804+
});
1805+
req.on("error", (err) => console.error(`[http] Request error on ${req.method} ${url.pathname}: ${err.message}`));
1806+
res.on("error", (err) => console.error(`[http] Response error on ${req.method} ${url.pathname}: ${err.message}`));
1807+
17581808
// ── Auth endpoints (always accessible) ──
17591809
if (url.pathname === "/api/auth" && req.method === "POST") {
17601810
const body = JSON.parse(await readBody(req));
@@ -2720,16 +2770,32 @@ a{color:#58a6ff;}</style></head>
27202770
}
27212771
});
27222772

2773+
httpServer.on("error", (err: Error) => {
2774+
console.error(`[http] Server error: ${err.message}\n${err.stack}`);
2775+
});
2776+
httpServer.on("clientError", (err: any, socket: any) => {
2777+
console.error(`[http] Client error: ${err.message}`);
2778+
try { socket.destroy(); } catch { /* no-op */ }
2779+
});
2780+
27232781
// WebSocket server — adapter-agnostic proxy
27242782
const wss = new WebSocketServer({ server: httpServer });
2783+
wss.on("error", (err: Error) => {
2784+
console.error(`[voice] WebSocket server error: ${err.message}\n${err.stack}`);
2785+
});
27252786

27262787
wss.on("connection", async (browserWs: WS, req: IncomingMessage) => {
27272788
// Check auth on WebSocket connections
27282789
if (!isAuthenticated(req)) {
2790+
console.warn(`[voice] Browser WS rejected (unauthorized) from ${req.socket.remoteAddress}`);
27292791
browserWs.close(4401, "Unauthorized");
27302792
return;
27312793
}
2732-
console.log(dim("[voice] Browser connected"));
2794+
const remote = req.socket.remoteAddress || "unknown";
2795+
console.log(dim(`[voice] Browser connected from ${remote}`));
2796+
browserWs.on("error", (err: Error) => {
2797+
console.error(`[voice] Browser WS error (${remote}): ${err.message}`);
2798+
});
27332799

27342800
// ── Per-connection frame buffer + moment capture state ──────────
27352801
let latestVideoFrame: { frame: string; mimeType: string; ts: number } | null = null;
@@ -2915,8 +2981,8 @@ a{color:#58a6ff;}</style></head>
29152981
});
29162982
}
29172983
adapter?.send(msg);
2918-
} catch {
2919-
// Ignore unparseable messages
2984+
} catch (err: any) {
2985+
console.error(`[voice] WS message handler error: ${err?.message || err}${err?.stack ? "\n" + err.stack : ""}`);
29202986
}
29212987
});
29222988

@@ -2945,6 +3011,11 @@ a{color:#58a6ff;}</style></head>
29453011

29463012
console.log(bold(`Voice server running on :${port}`));
29473013
console.log(dim(`[voice] Backend: ${opts.adapter}`));
3014+
console.log(dim(`[voice] Agent dir: ${agentRoot}`));
3015+
console.log(dim(`[voice] Model: ${opts.model || "(default)"}`));
3016+
console.log(dim(`[voice] Composio: ${composioAdapter ? "enabled" : "disabled"}`));
3017+
console.log(dim(`[voice] Telegram: ${telegramToken ? "configured" : "not configured"}`));
3018+
console.log(dim(`[voice] Auth: ${serverPassword ? "password-protected" : "open"}`));
29483019
console.log(dim(`[voice] Open http://localhost:${port} in your browser`));
29493020

29503021
// Start the cron scheduler

0 commit comments

Comments
 (0)