@@ -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+
5973function 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
8195installConsoleIntercept ( ) ;
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