Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/canwhite_krebs.json
T

1 line
115 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "canwhite/krebs", "files": {"server/index.ts": {"31959c65fbcff64269b9d89f7bb900b39c6c2bb4": {"t": 1785745560.6046867, "n": 11482, "h": "2e2ae255a0ca17bc", "c": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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": "/// <reference types=\"bun-types\" />\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<string, string> = {\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 <token> 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"}}}}