{"repo": "canwhite/krebs", "files": {"server/index.ts": {"31959c65fbcff64269b9d89f7bb900b39c6c2bb4": {"t": 1785745560.6046867, "n": 11482, "h": "2e2ae255a0ca17bc", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n lruSessionManager,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport { luaRuntime } from \"../tools/lua-runtime.js\";\nimport { loadLuaToolDefinitions } from \"../tools/lua-tools-registry.js\";\nimport { registerTool } from \"../tools/index.js\";\nimport { join } from \"node:path\";\nimport {\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\nconst corsHeaders = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n};\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Lua Tools Init ====================\nconst luaToolsPath = join(process.cwd(), \"lua-tools\");\n\nasync function initLuaTools() {\n try {\n await luaRuntime.initialize();\n const definitions = await loadLuaToolDefinitions(luaToolsPath);\n for (const def of definitions) {\n registerTool(def);\n }\n console.log(`[Lua] 已加载 ${definitions.length} 个工具`);\n } catch (error: any) {\n console.warn(`[Lua] 初始化失败: ${error.message}`);\n }\n}\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3333\", 10) : 3333;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\n(async () => {\n // 等待 Lua 工具初始化完成\n await initLuaTools();\n\n const server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: lruSessionManager.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId, undefined, true)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = lruSessionManager.getSession(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n lruSessionManager.removeSession(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 retry timer,防止 zombie retry\n const retryState = (ws as any).data?.retryState;\n if (retryState?.timer) {\n clearTimeout(retryState.timer);\n console.log(`[WebSocket] 已清理 retry timer: ${sessionId}`);\n }\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n})();\n"}, "6be8f40a68d4b41a6b69b9f0eafa490d7fabac5f": {"t": 1785754227.791595, "n": 11482, "h": "2e2ae255a0ca17bc", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n lruSessionManager,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport { luaRuntime } from \"../tools/lua-runtime.js\";\nimport { loadLuaToolDefinitions } from \"../tools/lua-tools-registry.js\";\nimport { registerTool } from \"../tools/index.js\";\nimport { join } from \"node:path\";\nimport {\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\nconst corsHeaders = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n};\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Lua Tools Init ====================\nconst luaToolsPath = join(process.cwd(), \"lua-tools\");\n\nasync function initLuaTools() {\n try {\n await luaRuntime.initialize();\n const definitions = await loadLuaToolDefinitions(luaToolsPath);\n for (const def of definitions) {\n registerTool(def);\n }\n console.log(`[Lua] 已加载 ${definitions.length} 个工具`);\n } catch (error: any) {\n console.warn(`[Lua] 初始化失败: ${error.message}`);\n }\n}\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3333\", 10) : 3333;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\n(async () => {\n // 等待 Lua 工具初始化完成\n await initLuaTools();\n\n const server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: lruSessionManager.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId, undefined, true)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = lruSessionManager.getSession(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n lruSessionManager.removeSession(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 retry timer,防止 zombie retry\n const retryState = (ws as any).data?.retryState;\n if (retryState?.timer) {\n clearTimeout(retryState.timer);\n console.log(`[WebSocket] 已清理 retry timer: ${sessionId}`);\n }\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n})();\n"}, "a8a60ab17556ac7892fb0ef0d518aa626ad55c97": {"t": 1785754229.2014751, "n": 11432, "h": "faff97bf5224d9e5", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n sessions,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport { luaRuntime } from \"../tools/lua-runtime.js\";\nimport { loadLuaToolDefinitions } from \"../tools/lua-tools-registry.js\";\nimport { registerTool } from \"../tools/index.js\";\nimport { join } from \"node:path\";\nimport {\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\nconst corsHeaders = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n};\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Lua Tools Init ====================\nconst luaToolsPath = join(process.cwd(), \"lua-tools\");\n\nasync function initLuaTools() {\n try {\n await luaRuntime.initialize();\n const definitions = await loadLuaToolDefinitions(luaToolsPath);\n for (const def of definitions) {\n registerTool(def);\n }\n console.log(`[Lua] 已加载 ${definitions.length} 个工具`);\n } catch (error: any) {\n console.warn(`[Lua] 初始化失败: ${error.message}`);\n }\n}\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3333\", 10) : 3333;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\n(async () => {\n // 等待 Lua 工具初始化完成\n await initLuaTools();\n\n const server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: sessions.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId, undefined, true)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = sessions.get(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n sessions.delete(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 retry timer,防止 zombie retry\n const retryState = (ws as any).data?.retryState;\n if (retryState?.timer) {\n clearTimeout(retryState.timer);\n console.log(`[WebSocket] 已清理 retry timer: ${sessionId}`);\n }\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n})();\n"}, "ebfd14dfe830f26b683b13ee244e197786aa8faa": {"t": 1785754232.4628396, "n": 11191, "h": "75e7ce303bff831f", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n sessions,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport { luaRuntime } from \"../tools/lua-runtime.js\";\nimport { loadLuaToolDefinitions } from \"../tools/lua-tools-registry.js\";\nimport { registerTool } from \"../tools/index.js\";\nimport { join } from \"node:path\";\nimport {\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\nconst corsHeaders = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n};\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Lua Tools Init ====================\nconst luaToolsPath = join(process.cwd(), \"lua-tools\");\n\nasync function initLuaTools() {\n try {\n await luaRuntime.initialize();\n const definitions = await loadLuaToolDefinitions(luaToolsPath);\n for (const def of definitions) {\n registerTool(def);\n }\n console.log(`[Lua] 已加载 ${definitions.length} 个工具`);\n } catch (error: any) {\n console.warn(`[Lua] 初始化失败: ${error.message}`);\n }\n}\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3333\", 10) : 3333;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\n(async () => {\n // 等待 Lua 工具初始化完成\n await initLuaTools();\n\n const server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: sessions.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId, undefined, true)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = sessions.get(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n sessions.delete(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n})();\n"}, "8bc20ed65905e71eb8a25d9506362a57ea4e165b": {"t": 1785754233.6754313, "n": 11196, "h": "5503789aa2b16075", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n sessions,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { corsHeaders } from \"./routes/index.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport { luaRuntime } from \"../tools/lua-runtime.js\";\nimport { loadLuaToolDefinitions } from \"../tools/lua-tools-registry.js\";\nimport { registerTool } from \"../tools/index.js\";\nimport { join } from \"node:path\";\nimport {\n createAuthHandler,\n createStopHandler,\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Lua Tools Init ====================\nconst luaToolsPath = join(process.cwd(), \"lua-tools\");\n\nasync function initLuaTools() {\n try {\n await luaRuntime.initialize();\n const definitions = await loadLuaToolDefinitions(luaToolsPath);\n for (const def of definitions) {\n registerTool(def);\n }\n console.log(`[Lua] 已加载 ${definitions.length} 个工具`);\n } catch (error: any) {\n console.warn(`[Lua] 初始化失败: ${error.message}`);\n }\n}\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3333\", 10) : 3333;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\n(async () => {\n // 等待 Lua 工具初始化完成\n await initLuaTools();\n\n const server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: sessions.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n authHandler: createAuthHandler(),\n stopHandler: createStopHandler(),\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId, undefined, true)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = sessions.get(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n sessions.delete(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n})();\n"}, "59644dde2f1071678750eeb93ebd9efc1b8ae856": {"t": 1785754235.2250237, "n": 11196, "h": "6d42c881f531b7a8", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n sessions,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { corsHeaders } from \"./routes/index.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport { luaRuntime } from \"../tools/lua-runtime.js\";\nimport { loadLuaToolDefinitions } from \"../tools/lua-tools-registry.js\";\nimport { registerTool } from \"../tools/index.js\";\nimport { join } from \"node:path\";\nimport {\n createAuthHandler,\n createStopHandler,\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Lua Tools Init ====================\nconst luaToolsPath = join(process.cwd(), \"lua-tools\");\n\nasync function initLuaTools() {\n try {\n await luaRuntime.initialize();\n const definitions = await loadLuaToolDefinitions(luaToolsPath);\n for (const def of definitions) {\n registerTool(def);\n }\n console.log(`[Lua] 已加载 ${definitions.length} 个工具`);\n } catch (error: any) {\n console.warn(`[Lua] 初始化失败: ${error.message}`);\n }\n}\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3000\", 10) : 3000;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\n(async () => {\n // 等待 Lua 工具初始化完成\n await initLuaTools();\n\n const server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: sessions.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n authHandler: createAuthHandler(),\n stopHandler: createStopHandler(),\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId, undefined, true)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = sessions.get(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n sessions.delete(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n})();\n"}, "9e60303c4170ff3c4c96883a0120cccea11891e7": {"t": 1785754236.400973, "n": 11179, "h": "417f46cb00155396", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n sessions,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { corsHeaders } from \"./routes/index.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport { luaRuntime } from \"../tools/lua-runtime.js\";\nimport { loadLuaToolDefinitions } from \"../tools/lua-tools-registry.js\";\nimport { registerTool } from \"../tools/index.js\";\nimport { join } from \"node:path\";\nimport {\n createAuthHandler,\n createStopHandler,\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Lua Tools Init ====================\nconst luaToolsPath = join(process.cwd(), \"lua-tools\");\n\nasync function initLuaTools() {\n try {\n await luaRuntime.initialize();\n const definitions = await loadLuaToolDefinitions(luaToolsPath);\n for (const def of definitions) {\n registerTool(def);\n }\n console.log(`[Lua] 已加载 ${definitions.length} 个工具`);\n } catch (error: any) {\n console.warn(`[Lua] 初始化失败: ${error.message}`);\n }\n}\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3000\", 10) : 3000;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\n(async () => {\n // 等待 Lua 工具初始化完成\n await initLuaTools();\n\n const server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: sessions.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n authHandler: createAuthHandler(),\n stopHandler: createStopHandler(),\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = sessions.get(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n sessions.delete(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n})();\n"}, "5bf4551dfbae356fe96729f74403846bdba7b2f5": {"t": 1785754237.6283906, "n": 10432, "h": "08bffec5b7c42c01", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n sessions,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { corsHeaders } from \"./routes/index.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\nimport { createWsRouter, type WsRouter } from \"./ws-router.js\";\nimport {\n createAuthHandler,\n createStopHandler,\n createSwitchSessionHandler,\n createPromptHandler,\n} from \"./handlers/index.js\";\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3000\", 10) : 3000;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\nconst server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: sessions.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n\n // 创建路由器(每个连接一个实例)\n const router: WsRouter = createWsRouter({\n authHandler: createAuthHandler(),\n stopHandler: createStopHandler(),\n switchSessionHandler: createSwitchSessionHandler(),\n promptHandler: createPromptHandler(),\n });\n\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n router,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = sessions.get(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n sessions.delete(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n message(ws: any, message: any) {\n const router: WsRouter = (ws as any).data?.router;\n if (!router) {\n ws.send(JSON.stringify({ type: \"error\", message: \"Router 未初始化\" }));\n return;\n }\n\n // 认证检查(auth 消息在路由器内部处理)\n const data = JSON.parse(message.toString()) as { type?: string };\n if (data.type !== \"auth\") {\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n // 检查 session 是否存在\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n }\n\n // 路由消息\n router.handleMessage(ws, message);\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n"}, "e8bd8fec1e42b0996007b2903dba15ed4cefe374": {"t": 1785754238.9209096, "n": 15991, "h": "22dd16cc8ece2a65", "c": "/// \nimport { MonitorLogger } from \"../lib/logger.js\";\nimport { extractFromSessionText } from \"../lib/session-content-extractor.js\";\nimport {\n saveSessionMeta,\n getAllSessions,\n getSessionById,\n} from \"../db/index.js\";\nimport {\n createRuntime,\n getSession,\n deleteSession,\n generateSessionId,\n sessions,\n MODEL_CONFIG,\n} from \"./session-service.js\";\nimport { subscribeToSessionEvents } from \"./event-subscription.js\";\nimport { cleanupThinkParserState } from \"./think-parser.js\";\nimport { extractToken, isValidToken, initToken } from \"./auth.js\";\nimport { corsHeaders } from \"./routes/index.js\";\nimport { handleApiMessage } from \"./routes/messages.js\";\nimport {\n handleCreateSession,\n handleDeleteSession,\n getSessionMessages,\n} from \"./routes/sessions.js\";\nimport { handleInternalAuth, handleExternalAuth } from \"./routes/auth.js\";\n\n// ==================== Init ====================\nif (!MODEL_CONFIG.apiKey) {\n console.error(\"错误:未设置 API Key 环境变量\");\n console.error(\"请设置以下之一:\");\n console.error(\" API_KEY=your_key_here # 推荐,通用配置\");\n console.error(\" DEEPSEEK_API_KEY=your_key_here # 兼容旧配置\");\n console.error(\" ANTHROPIC_API_KEY=your_key_here # 使用 Claude\");\n process.exit(1);\n}\n\ninitToken();\n\n// ==================== Constants ====================\nconst PORT = process.env.PORT ? parseInt(process.env.PORT ?? \"3000\", 10) : 3000;\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.split(\".\").pop();\n const types: Record = {\n js: \"application/javascript; charset=utf-8\",\n ts: \"application/javascript; charset=utf-8\",\n tsx: \"application/javascript; charset=utf-8\",\n jsx: \"application/javascript; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n svg: \"image/svg+xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n };\n return types[ext || \"\"] || \"application/octet-stream\";\n}\n\n// ==================== Server ====================\nconst server = Bun.serve({\n port: PORT,\n async fetch(req: Request) {\n const url = new URL(req.url);\n\n if (req.method === \"OPTIONS\") {\n return new Response(null, { headers: corsHeaders });\n }\n\n // 内部认证接口(本地前端自动认证)\n if (url.pathname === \"/api/auth/internal\") {\n return handleInternalAuth();\n }\n\n // 外部认证接口(需要手动传递 token)\n if (url.pathname === \"/api/auth\" && req.method === \"POST\") {\n return handleExternalAuth(req);\n }\n\n if (url.pathname === \"/ws\") {\n const upgraded = server.upgrade(req);\n if (upgraded) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 400 });\n }\n\n // API 接口需要 Token 验证(除了认证接口)\n if (\n url.pathname.startsWith(\"/api/\") &&\n !url.pathname.startsWith(\"/api/auth\")\n ) {\n const token = extractToken(req);\n if (!isValidToken(token)) {\n return Response.json(\n {\n error: \"未授权,请先认证\",\n hint: \"使用 POST /api/auth 并提供 token,或使用 Authorization: Bearer header\",\n },\n { status: 401, headers: corsHeaders },\n );\n }\n }\n\n if (url.pathname === \"/api/messages\" && req.method === \"POST\") {\n return handleApiMessage(req);\n }\n\n if (url.pathname === \"/api/sessions\" && req.method === \"POST\") {\n return handleCreateSession();\n }\n\n if (url.pathname === \"/api/sessions/list\" && req.method === \"GET\") {\n const allSessions = getAllSessions();\n return Response.json({ sessions: allSessions }, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"GET\") {\n const id = url.pathname.split(\"/\").pop()!;\n const sessionMessages = await getSessionMessages(id);\n if (!sessionMessages) {\n return Response.json(\n { error: \"Session 不存在\" },\n { status: 404, headers: corsHeaders },\n );\n }\n return Response.json(sessionMessages, { headers: corsHeaders });\n }\n\n if (url.pathname.startsWith(\"/api/sessions/\") && req.method === \"DELETE\") {\n const sessionId = url.pathname.split(\"/\").pop()!;\n return handleDeleteSession(sessionId);\n }\n\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n return Response.json(\n { status: \"ok\", sessions: sessions.size },\n { headers: corsHeaders },\n );\n }\n\n // Serve chat UI\n if (url.pathname === \"/\" && req.method === \"GET\") {\n return new Response(Bun.file(\"./frontend/chat.html\"), {\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n // Serve frontend static files with Bun's transpiler\n if (url.pathname.startsWith(\"/frontend/\")) {\n const filePath = \".\" + url.pathname;\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n // For TSX/JSX files, transpile on the fly\n if (filePath.endsWith(\".tsx\") || filePath.endsWith(\".jsx\")) {\n const transpiled = await Bun.build({\n entrypoints: [filePath],\n target: \"browser\",\n minify: false,\n jsx: {\n runtime: \"automatic\",\n importSource: \"react\",\n },\n });\n\n return new Response(transpiled.outputs[0], {\n headers: {\n \"Content-Type\": \"application/javascript; charset=utf-8\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n\n return new Response(file, {\n headers: {\n \"Content-Type\": getContentType(filePath),\n \"Access-Control-Allow-Origin\": \"*\",\n },\n });\n }\n }\n\n return new Response(\"Not Found\", { status: 404, headers: corsHeaders });\n },\n websocket: {\n open(ws: any) {\n console.log(\"[WebSocket] 新连接已建立\");\n const sessionId = generateSessionId();\n\n // Create logger instance\n const logger = MonitorLogger.createInstance();\n (ws as any).data = {\n sessionId,\n logger,\n authenticated: false,\n };\n\n logger.log(`[SESSION] Session ${sessionId} started, WebSocket opened`);\n\n createRuntime(sessionId)\n .then((result) => {\n const runtime = result.runtime;\n const session = runtime.session;\n (ws as any).data.runtime = runtime;\n (ws as any).data.session = session;\n (ws as any).data.firstMessageSaved = false;\n ws.send(\n JSON.stringify({\n type: \"connected\",\n sessionId,\n message: \"WebSocket 连接已建立\",\n }),\n );\n\n logger.log(`[SESSION] Session created successfully`);\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(ws, session, logger);\n (ws as any).data.unsubscribe = unsubscribe;\n })\n .catch((error) => {\n console.error(`[WebSocket] Session 创建失败: ${error.message}`);\n logger.log(`[ERROR] Session 创建失败: ${error.message}`);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 创建失败,请重试\",\n }),\n );\n\n const tempSessionId = (ws as any).data?.sessionId;\n if (tempSessionId) {\n const tempRuntime = sessions.get(tempSessionId);\n if (tempRuntime) {\n try {\n tempRuntime.dispose();\n sessions.delete(tempSessionId);\n console.log(\n `[WebSocket] 已清理失败的 runtime: ${tempSessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理失败 runtime 时出错:`, e);\n }\n }\n }\n\n ws.close(1011, \"Session creation failed\");\n });\n },\n async message(ws: any, message: any) {\n try {\n const data = JSON.parse(message.toString()) as {\n type?: string;\n message?: string;\n sessionId?: string;\n token?: string;\n };\n\n // 认证消息处理(本地前端直接信任)\n if (data.type === \"auth\") {\n (ws as any).data.authenticated = true;\n console.log(\"[WebSocket] 认证成功(本地前端)\");\n ws.send(JSON.stringify({ type: \"auth_success\" }));\n return;\n }\n\n // 其他消息需要先认证\n if (!(ws as any).data?.authenticated) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"未认证,请先发送 auth 消息\",\n }),\n );\n ws.close(1008, \"Unauthorized\");\n return;\n }\n\n const sessionId = (ws as any).data?.sessionId;\n const runtime = getSession(sessionId!);\n\n if (!runtime) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在\",\n }),\n );\n return;\n }\n\n const session = runtime.session;\n\n if (data.type === \"stop\") {\n console.log(`[WebSocket] 收到停止请求`);\n if (session.isStreaming) {\n await session.abort();\n console.log(`[WebSocket] 已停止 AI 回复`);\n }\n return;\n }\n\n if (data.type === \"switch_session\" && data.sessionId) {\n if ((ws as any).data.isSwitchingSession) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 切换中,请稍后\",\n }),\n );\n return;\n }\n\n console.log(`[WebSocket] 切换 session 到: ${data.sessionId}`);\n\n const sessionMeta = getSessionById(data.sessionId);\n if (!sessionMeta || !sessionMeta.file_path) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"Session 不存在或文件路径无效\",\n }),\n );\n return;\n }\n\n (ws as any).data.isSwitchingSession = true;\n\n try {\n const runtime = (ws as any).data.runtime as any;\n if (!runtime) {\n throw new Error(\"Runtime 不存在\");\n }\n\n // 取消旧的事件订阅\n const oldUnsubscribe = (ws as any).data.unsubscribe;\n if (oldUnsubscribe) {\n try {\n oldUnsubscribe();\n console.log(`[WebSocket] 已取消旧 session 的事件订阅`);\n } catch (e) {\n console.error(`[WebSocket] 取消旧订阅失败:`, e);\n }\n }\n\n const result = await runtime.switchSession(sessionMeta.file_path);\n if (!result.cancelled) {\n // 更新 session 引用\n const newSession = runtime.session;\n\n // 使用共享的事件订阅工厂\n const unsubscribe = subscribeToSessionEvents(\n ws,\n newSession,\n (ws as any).data.logger,\n );\n (ws as any).data.unsubscribe = unsubscribe;\n\n // P1-1 修复: 检查目标 sessionId 是否已存在,清理旧 runtime\n const existingRuntime = sessions.get(data.sessionId);\n if (existingRuntime && existingRuntime !== runtime) {\n try {\n existingRuntime.dispose();\n console.log(\n `[WebSocket] 已清理被覆盖的 runtime: ${data.sessionId}`,\n );\n } catch (e) {\n console.error(`[WebSocket] 清理旧 runtime 失败:`, e);\n }\n }\n\n // Update sessions Map mapping\n sessions.set(data.sessionId, runtime);\n\n // Remove old sessionId mapping if different\n if (sessionId && sessionId !== data.sessionId) {\n sessions.delete(sessionId);\n }\n\n // Update the sessionId in ws.data\n (ws as any).data.sessionId = data.sessionId;\n (ws as any).data.firstMessageSaved = true;\n (ws as any).data.session = newSession;\n\n ws.send(\n JSON.stringify({\n type: \"session_switched\",\n sessionId: data.sessionId,\n }),\n );\n console.log(`[WebSocket] Session 切换成功: ${data.sessionId}`);\n } else {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"切换 session 被取消\",\n }),\n );\n }\n } catch (error: any) {\n console.error(`[WebSocket] 切换 session 出错:`, error);\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"切换 session 出错,请重试\",\n }),\n );\n } finally {\n (ws as any).data.isSwitchingSession = false;\n }\n } else if (data.type === \"prompt\" && typeof data.message === \"string\") {\n console.log(`[WebSocket] 收到消息: ${data.message}`);\n\n const firstMessageSaved = (ws as any).data?.firstMessageSaved;\n if (!firstMessageSaved) {\n const filePath = session.sessionFile;\n\n if (filePath) {\n saveSessionMeta(sessionId!, data.message, filePath);\n (ws as any).data.firstMessageSaved = true;\n }\n }\n\n if (session.isStreaming) {\n console.log(`[WebSocket] 会话正在响应中,将新消息加入队列`);\n\n ws.send(\n JSON.stringify({\n type: \"question_queued\",\n question: data.message,\n }),\n );\n\n session.followUp(data.message).catch((error) => {\n const errorMessage = error?.message || \"未知错误\";\n const wsLogger = (ws as any).data?.logger;\n if (wsLogger) {\n wsLogger.log(`[ERROR] followUp 失败: ${errorMessage}`);\n }\n console.error(`[WebSocket] followUp 失败:`, error);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"消息处理失败,请重试\",\n }),\n );\n });\n } else {\n session.prompt(data.message).catch((error) => {\n const errorMessage = error?.message || \"未知错误\";\n const wsLogger = (ws as any).data?.logger;\n if (wsLogger) {\n wsLogger.log(`[ERROR] prompt 失败: ${errorMessage}`);\n }\n console.error(`[WebSocket] prompt 失败:`, error);\n\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: \"消息处理失败,请重试\",\n }),\n );\n });\n }\n }\n } catch (error: any) {\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: `解析消息失败: ${error.message}`,\n }),\n );\n }\n },\n close(ws: any) {\n const sessionId = (ws as any).data?.sessionId;\n const logger = (ws as any).data?.logger;\n console.log(`[WebSocket] 连接已关闭: ${sessionId}`);\n\n // 清理 think 解析器状态\n cleanupThinkParserState(ws);\n\n // 清理事件订阅,防止内存泄漏\n const unsubscribe = (ws as any).data?.unsubscribe;\n if (unsubscribe) {\n try {\n unsubscribe();\n console.log(`[WebSocket] 已清理事件订阅: ${sessionId}`);\n } catch (error) {\n console.error(`[WebSocket] 清理订阅失败: ${error}`);\n }\n }\n\n if (logger) {\n try {\n logger.log(`[SESSION] Session ${sessionId} WebSocket closed`);\n logger.close();\n } catch (error) {\n console.error(`[WebSocket] Logger 关闭失败:`, error);\n }\n }\n\n // 清理 runtime (P0 修复)\n if (sessionId) {\n deleteSession(sessionId);\n }\n\n // 清理 ws.data 引用\n (ws as any).data = null;\n },\n },\n});\n\nconsole.log(\"=\".repeat(60));\nconsole.log(\"🚀 Krebs Gateway 已启动!\");\nconsole.log(\"=\".repeat(60));\nconsole.log(`📡 HTTP: http://localhost:${server.port}`);\nconsole.log(`🔌 WebSocket: ws://localhost:${server.port}/ws`);\nconsole.log(\"=\".repeat(60));\n"}}}}