diff --git a/packages/adapter-utils/package.json b/packages/adapter-utils/package.json index 8a9411af..118eb895 100644 --- a/packages/adapter-utils/package.json +++ b/packages/adapter-utils/package.json @@ -30,6 +30,7 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { + "@types/node": "^24.6.0", "typescript": "^5.7.3" } } diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 1c8b76bd..76efba86 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -15,6 +15,14 @@ interface RunningProcess { graceSec: number; } +type ChildProcessWithEvents = ChildProcess & { + on(event: "error", listener: (err: Error) => void): ChildProcess; + on( + event: "close", + listener: (code: number | null, signal: NodeJS.Signals | null) => void, + ): ChildProcess; +}; + export const runningProcesses = new Map(); export const MAX_CAPTURE_BYTES = 4 * 1024 * 1024; export const MAX_EXCERPT_BYTES = 32 * 1024; @@ -217,7 +225,7 @@ export async function runChildProcess( env: mergedEnv, shell: false, stdio: [opts.stdin != null ? "pipe" : "ignore", "pipe", "pipe"], - }); + }) as ChildProcessWithEvents; if (opts.stdin != null && child.stdin) { child.stdin.write(opts.stdin); @@ -244,7 +252,7 @@ export async function runChildProcess( }, opts.timeoutSec * 1000) : null; - child.stdout?.on("data", (chunk) => { + child.stdout?.on("data", (chunk: unknown) => { const text = String(chunk); stdout = appendWithCap(stdout, text); logChain = logChain @@ -252,7 +260,7 @@ export async function runChildProcess( .catch((err) => onLogError(err, runId, "failed to append stdout log chunk")); }); - child.stderr?.on("data", (chunk) => { + child.stderr?.on("data", (chunk: unknown) => { const text = String(chunk); stderr = appendWithCap(stderr, text); logChain = logChain @@ -260,7 +268,7 @@ export async function runChildProcess( .catch((err) => onLogError(err, runId, "failed to append stderr log chunk")); }); - child.on("error", (err) => { + child.on("error", (err: Error) => { if (timeout) clearTimeout(timeout); runningProcesses.delete(runId); const errno = (err as NodeJS.ErrnoException).code; @@ -272,7 +280,7 @@ export async function runChildProcess( reject(new Error(msg)); }); - child.on("close", (code, signal) => { + child.on("close", (code: number | null, signal: NodeJS.Signals | null) => { if (timeout) clearTimeout(timeout); runningProcesses.delete(runId); void logChain.finally(() => { diff --git a/packages/adapters/claude-local/package.json b/packages/adapters/claude-local/package.json index faa16b64..c999013d 100644 --- a/packages/adapters/claude-local/package.json +++ b/packages/adapters/claude-local/package.json @@ -45,6 +45,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { + "@types/node": "^24.6.0", "typescript": "^5.7.3" } } diff --git a/packages/adapters/codex-local/package.json b/packages/adapters/codex-local/package.json index 9fc9b581..e6853aa7 100644 --- a/packages/adapters/codex-local/package.json +++ b/packages/adapters/codex-local/package.json @@ -45,6 +45,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { + "@types/node": "^24.6.0", "typescript": "^5.7.3" } } diff --git a/packages/adapters/openclaw/package.json b/packages/adapters/openclaw/package.json index 22acb5e3..c8bd561d 100644 --- a/packages/adapters/openclaw/package.json +++ b/packages/adapters/openclaw/package.json @@ -44,6 +44,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { + "@types/node": "^24.6.0", "typescript": "^5.7.3" } } diff --git a/packages/db/package.json b/packages/db/package.json index 845d5487..0a0b4521 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -38,6 +38,7 @@ "postgres": "^3.4.5" }, "devDependencies": { + "@types/node": "^24.6.0", "drizzle-kit": "^0.31.9", "tsx": "^4.19.2", "typescript": "^5.7.3", diff --git a/server/package.json b/server/package.json index 781f452a..e479eafe 100644 --- a/server/package.json +++ b/server/package.json @@ -58,6 +58,7 @@ "@types/express-serve-static-core": "^5.0.0", "@types/multer": "^2.0.0", "@types/supertest": "^6.0.2", + "@types/ws": "^8.18.1", "supertest": "^7.0.0", "tsx": "^4.19.2", "typescript": "^5.7.3", diff --git a/server/src/index.ts b/server/src/index.ts index ada5743f..125d3021 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -444,7 +444,7 @@ const app = await createApp(db as any, { betterAuthHandler, resolveSession, }); -const server = createServer(app); +const server = createServer(app as unknown as Parameters[0]); const listenPort = await detectPort(config.port); if (listenPort !== config.port) { diff --git a/server/src/realtime/live-events-ws.ts b/server/src/realtime/live-events-ws.ts index b082ecb6..d18e2930 100644 --- a/server/src/realtime/live-events-ws.ts +++ b/server/src/realtime/live-events-ws.ts @@ -1,15 +1,45 @@ import { createHash } from "node:crypto"; import type { IncomingMessage, Server as HttpServer } from "node:http"; +import { createRequire } from "node:module"; import type { Duplex } from "node:stream"; import { and, eq, isNull } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { agentApiKeys, companyMemberships, instanceUserRoles } from "@paperclipai/db"; import type { DeploymentMode } from "@paperclipai/shared"; -import { WebSocket, WebSocketServer } from "ws"; import type { BetterAuthSessionResult } from "../auth/better-auth.js"; import { logger } from "../middleware/logger.js"; import { subscribeCompanyLiveEvents } from "../services/live-events.js"; +interface WsSocket { + readyState: number; + ping(): void; + send(data: string): void; + terminate(): void; + close(code?: number, reason?: string): void; + on(event: "pong", listener: () => void): void; + on(event: "close", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; +} + +interface WsServer { + clients: Set; + on(event: "connection", listener: (socket: WsSocket, req: IncomingMessage) => void): void; + on(event: "close", listener: () => void): void; + handleUpgrade( + req: IncomingMessage, + socket: Duplex, + head: Buffer, + callback: (ws: WsSocket) => void, + ): void; + emit(event: "connection", ws: WsSocket, req: IncomingMessage): boolean; +} + +const require = createRequire(import.meta.url); +const { WebSocket, WebSocketServer } = require("ws") as { + WebSocket: { OPEN: number }; + WebSocketServer: new (opts: { noServer: boolean }) => WsServer; +}; + interface UpgradeContext { companyId: string; actorType: "board" | "agent"; @@ -154,8 +184,8 @@ export function setupLiveEventsWebSocketServer( }, ) { const wss = new WebSocketServer({ noServer: true }); - const cleanupByClient = new Map void>(); - const aliveByClient = new Map(); + const cleanupByClient = new Map void>(); + const aliveByClient = new Map(); const pingInterval = setInterval(() => { for (const socket of wss.clients) { @@ -168,7 +198,7 @@ export function setupLiveEventsWebSocketServer( } }, 30000); - wss.on("connection", (socket, req) => { + wss.on("connection", (socket: WsSocket, req: IncomingMessage) => { const context = (req as IncomingMessageWithContext).paperclipUpgradeContext; if (!context) { socket.close(1008, "missing context"); @@ -194,7 +224,7 @@ export function setupLiveEventsWebSocketServer( aliveByClient.delete(socket); }); - socket.on("error", (err) => { + socket.on("error", (err: Error) => { logger.warn({ err, companyId: context.companyId }, "live websocket client error"); }); }); @@ -229,7 +259,7 @@ export function setupLiveEventsWebSocketServer( const reqWithContext = req as IncomingMessageWithContext; reqWithContext.paperclipUpgradeContext = context; - wss.handleUpgrade(req, socket, head, (ws) => { + wss.handleUpgrade(req, socket, head, (ws: WsSocket) => { wss.emit("connection", ws, reqWithContext); }); })