Merge branch 'main' of github.com:getpaseo/paseo

This commit is contained in:
Mohamed Boudra
2026-04-30 10:38:26 +07:00
3 changed files with 37 additions and 13 deletions

View File

@@ -207,19 +207,23 @@ async function main() {
process.on("uncaughtException", (err) => { process.on("uncaughtException", (err) => {
logger.fatal({ err }, "Uncaught exception — daemon crashing"); logger.fatal({ err }, "Uncaught exception — daemon crashing");
process.exit(1); exitAfterPinoFlush();
}); });
process.on("unhandledRejection", (reason) => { process.on("unhandledRejection", (reason) => {
logger.fatal({ err: reason }, "Unhandled promise rejection — daemon crashing"); logger.fatal({ err: reason }, "Unhandled promise rejection — daemon crashing");
process.exit(1); exitAfterPinoFlush();
}); });
} }
// Give pino async streams a moment to flush the fatal log entry to daemon.log
// before the process exits. Without this, the last few entries that explain
// why the daemon crashed can be lost.
function exitAfterPinoFlush(): void {
setTimeout(() => process.exit(1), 200);
}
main().catch((err) => { main().catch((err) => {
process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`); process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
// Give pino streams a moment to flush the fatal log entry to daemon.log exitAfterPinoFlush();
// before the process exits. Without this, async file streams may lose the
// last few entries that explain why the daemon crashed.
setTimeout(() => process.exit(1), 200);
}); });

View File

@@ -396,7 +396,7 @@ async function attachEncryptedSocket(
metadata?: ExternalSocketMetadata, metadata?: ExternalSocketMetadata,
): Promise<void> { ): Promise<void> {
try { try {
const relayTransport = createRelayTransportAdapter(socket); const relayTransport = createRelayTransportAdapter(socket, logger);
const emitter = new EventEmitter(); const emitter = new EventEmitter();
const channel = await createDaemonChannel(relayTransport, daemonKeyPair, { const channel = await createDaemonChannel(relayTransport, daemonKeyPair, {
onmessage: (data) => emitter.emit("message", data), onmessage: (data) => emitter.emit("message", data),
@@ -418,9 +418,18 @@ async function attachEncryptedSocket(
} }
} }
function createRelayTransportAdapter(socket: WebSocket): RelayTransport { function createRelayTransportAdapter(socket: WebSocket, logger: pino.Logger): RelayTransport {
const relayTransport: RelayTransport = { const relayTransport: RelayTransport = {
send: (data) => socket.send(data), send: (data) => {
try {
socket.send(data);
} catch (err) {
// Socket likely transitioned to closed between checks; let onclose/onerror
// drive cleanup. Without this guard the synchronous throw would propagate
// up as an uncaughtException and take down the daemon.
logger.warn({ err }, "relay_socket_send_failed");
}
},
close: (code?: number, reason?: string) => socket.close(code, reason), close: (code?: number, reason?: string) => socket.close(code, reason),
onmessage: null, onmessage: null,
onclose: null, onclose: null,

View File

@@ -738,10 +738,17 @@ export class VoiceAssistantWebSocketServer {
} }
private sendToClient(ws: WebSocketLike, message: WSOutboundMessage): void { private sendToClient(ws: WebSocketLike, message: WSOutboundMessage): void {
// WebSocket.OPEN = 1 // WebSocket.OPEN = 1. The check is a fast path; the socket can still
if (ws.readyState === 1) { // transition to closed between here and ws.send(), so guard the send too —
// a synchronous throw here would propagate as an uncaughtException.
if (ws.readyState !== 1) {
return;
}
try {
ws.send(JSON.stringify(message)); ws.send(JSON.stringify(message));
this.recordOutboundMessage(message, ws); this.recordOutboundMessage(message, ws);
} catch (err) {
this.logger.warn({ err }, "ws_send_failed");
} }
} }
@@ -749,8 +756,12 @@ export class VoiceAssistantWebSocketServer {
if (ws.readyState !== 1) { if (ws.readyState !== 1) {
return; return;
} }
ws.send(frame); try {
this.recordOutboundBinaryFrame(ws); ws.send(frame);
this.recordOutboundBinaryFrame(ws);
} catch (err) {
this.logger.warn({ err }, "ws_send_binary_failed");
}
} }
private sendToConnection(connection: SessionConnection, message: WSOutboundMessage): void { private sendToConnection(connection: SessionConnection, message: WSOutboundMessage): void {