agentdev 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/BasicAgent-7TNMYC3X.js +13 -0
  2. package/dist/ExplorerAgent-L3ZTVQGM.js +13 -0
  3. package/dist/{chunk-QFHPUAUQ.js → chunk-35LBACUK.js} +8 -8
  4. package/dist/{chunk-QFHPUAUQ.js.map → chunk-35LBACUK.js.map} +1 -1
  5. package/dist/{chunk-NORTAQIL.js → chunk-4WK7UENZ.js} +1011 -11
  6. package/dist/chunk-4WK7UENZ.js.map +1 -0
  7. package/dist/{chunk-BAP2GCYH.js → chunk-7GTVQ55R.js} +1 -1
  8. package/dist/chunk-7GTVQ55R.js.map +1 -0
  9. package/dist/{chunk-G5ECPY4K.js → chunk-EK6KGS2M.js} +87 -8
  10. package/dist/{chunk-G5ECPY4K.js.map → chunk-EK6KGS2M.js.map} +1 -1
  11. package/dist/{chunk-5T4C2XRT.js → chunk-KE3KYZVJ.js} +21 -8
  12. package/dist/chunk-KE3KYZVJ.js.map +1 -0
  13. package/dist/{chunk-EECW6PYP.js → chunk-UL2ZBPBL.js} +60 -4
  14. package/dist/chunk-UL2ZBPBL.js.map +1 -0
  15. package/dist/{chunk-A354ZCZF.js → chunk-XRB6MD2J.js} +5593 -982
  16. package/dist/chunk-XRB6MD2J.js.map +1 -0
  17. package/dist/cli/server.js +2 -2
  18. package/dist/cli/viewer.js +2 -2
  19. package/dist/create-feature-cli.js +26 -7
  20. package/dist/features/mcp/templates/mcp-tool.render.js +14 -1
  21. package/dist/features/mcp/templates/mcp-tool.render.js.map +1 -1
  22. package/dist/features/shell/templates/bash.render.d.ts +1 -1
  23. package/dist/features/websearch/templates/web-fetch.render.d.ts +1 -1
  24. package/dist/index.d.ts +778 -21
  25. package/dist/index.js +17 -7
  26. package/dist/index.js.map +1 -1
  27. package/dist/{notification-NWVOS2WR.js → notification-QPH37BHW.js} +29 -6
  28. package/dist/notification-QPH37BHW.js.map +1 -0
  29. package/dist/{tools-LDR3LIJP.js → tools-OKH7SPMP.js} +2 -2
  30. package/dist/{types-CF5UsxD9.d.ts → types-NVwNUVFR.d.ts} +180 -14
  31. package/package.json +7 -14
  32. package/dist/BasicAgent-UWXLSZP2.js +0 -13
  33. package/dist/ExplorerAgent-LCM3JQS4.js +0 -13
  34. package/dist/chunk-5T4C2XRT.js.map +0 -1
  35. package/dist/chunk-A354ZCZF.js.map +0 -1
  36. package/dist/chunk-BAP2GCYH.js.map +0 -1
  37. package/dist/chunk-EECW6PYP.js.map +0 -1
  38. package/dist/chunk-NORTAQIL.js.map +0 -1
  39. package/dist/notification-NWVOS2WR.js.map +0 -1
  40. /package/dist/{BasicAgent-UWXLSZP2.js.map → BasicAgent-7TNMYC3X.js.map} +0 -0
  41. /package/dist/{ExplorerAgent-LCM3JQS4.js.map → ExplorerAgent-L3ZTVQGM.js.map} +0 -0
  42. /package/dist/{tools-LDR3LIJP.js.map → tools-OKH7SPMP.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/viewer-worker.ts","../src/core/debugger-mcp.ts","../src/core/render.ts","../src/core/viewer-html.ts"],"sourcesContent":["\n/**\n * Viewer Worker - 在独立进程中运行 HTTP 服务器\n * 支持多 Agent 调试,共享单端口\n * 支持通过 UDS(Unix Domain Socket)或 Windows Named Pipe 接收来自多进程的连接\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from 'http';\nimport { createServer as createNetServer, Server, Socket } from 'net';\nimport { unlinkSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { type Message, type Tool, type DebugLogEntry, type AgentOverviewSnapshot, type AgentRuntimeSnapshot, AgentSession, DebugHubIPCMessage, ToolMetadata, getDefaultUDSPath } from './types.js';\nimport {\n DebuggerMCPServer,\n DEBUGGER_MCP_PROMPT_DEFINITIONS,\n DEBUGGER_MCP_RESOURCE_DEFINITIONS,\n DEBUGGER_MCP_TOOL_DEFINITIONS,\n createDebuggerAgentDetails,\n createDebuggerAgentSummary,\n filterDebuggerLogs,\n type DebuggerLogQuery,\n} from './debugger-mcp.js';\nimport {\n RENDER_TEMPLATES,\n SYSTEM_RENDER_MAP,\n TOOL_DISPLAY_NAMES,\n getToolRenderConfig\n} from './render.js';\nimport { generateViewerHtml } from './viewer-html.js';\n\nconst QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT = 200;\n\n// ============= Worker 类 =============\n\nclass ViewerWorker {\n private port: number;\n private openBrowser: boolean;\n private server: ReturnType<typeof createServer>;\n private udsPath: string;\n private udsServer?: Server;\n private udsClients: Map<string, Socket> = new Map();\n\n // 多 Agent 会话存储\n private agentSessions: Map<string, AgentSession> = new Map();\n\n // 当前选中的 Agent ID\n private currentAgentId: string | null = null;\n\n // Feature 模板路径映射(agentId -> 模板名 -> URL)\n private featureTemplateMap: Map<string, Record<string, string>> = new Map();\n\n private readonly debuggerMcp = new DebuggerMCPServer({\n listAgents: () => this.listAgentSummaries(),\n getAgent: (agentId: string) => this.getAgentDetails(agentId),\n getCurrentAgentId: () => this.currentAgentId,\n getHooks: (agentId: string) => this.agentSessions.get(agentId)?.hookInspector,\n queryLogs: (query: DebuggerLogQuery) => this.queryLogs(query),\n });\n\n // 内存限制配置\n private readonly MAX_MESSAGES = 10000;\n private readonly MAX_BYTES = 50 * 1024 * 1024; // 50MB\n private readonly MAX_LOGS = 5000;\n\n constructor(port: number, openBrowser: boolean = true, udsPath?: string) {\n this.port = port;\n this.openBrowser = openBrowser;\n this.udsPath = udsPath || getDefaultUDSPath();\n this.server = createServer();\n }\n\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n // 先启动 UDS 服务器\n this.startUDSServer();\n\n // 再启动 HTTP 服务器\n this.server.on('request', (req, res) => this.handleRequest(req, res));\n\n this.server.on('error', (err: any) => {\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`端口 ${this.port} 被占用`));\n } else {\n reject(err);\n }\n });\n\n this.server.listen(this.port, async () => {\n const url = `http://localhost:${this.port}`;\n console.log(`[Viewer Worker] ${url}`);\n console.log(`[Viewer Worker] MCP endpoint: ${url}/mcp`);\n\n // 打开浏览器(仅在 openBrowser 为 true 时)\n if (this.openBrowser) {\n try {\n const open = await import('open');\n await open.default(url).catch(() => {\n console.warn('[Viewer Worker] 浏览器打开失败,请手动访问: ' + url);\n });\n } catch {\n console.warn('[Viewer Worker] open 模块不可用,请手动访问: ' + url);\n }\n }\n\n // 通知主进程服务器已启动\n if (process.send) {\n process.send({ type: 'ready' });\n }\n\n resolve();\n });\n });\n }\n\n /**\n * 停止服务器\n */\n async stop(): Promise<void> {\n return new Promise((resolve) => {\n // 关闭 HTTP 服务器\n if (this.server) {\n this.server.close(() => {\n console.log('[Viewer Worker] HTTP 服务器已关闭');\n });\n }\n\n // 关闭 UDS 服务器\n if (this.udsServer) {\n this.udsServer.close(() => {\n console.log('[Viewer Worker] UDS 服务器已关闭');\n });\n }\n\n // 关闭所有 UDS 客户端连接\n for (const [id, socket] of this.udsClients) {\n socket.destroy();\n console.log(`[Viewer Worker] 客户端连接已关闭: ${id}`);\n }\n this.udsClients.clear();\n\n // 清理 Unix socket 文件(非 Windows)\n if (process.platform !== 'win32' && this.udsPath && existsSync(this.udsPath)) {\n try {\n unlinkSync(this.udsPath);\n } catch {}\n }\n\n resolve();\n });\n }\n\n // ========== UDS 服务器 ==========\n\n /**\n * 启动 UDS 服务器\n */\n private startUDSServer(): void {\n // 清理旧 socket 文件(非 Windows)\n if (process.platform !== 'win32' && existsSync(this.udsPath)) {\n try {\n unlinkSync(this.udsPath);\n } catch {}\n }\n\n // 客户端连接计数器,用于生成唯一 ID\n let connectionCounter = 0;\n\n this.udsServer = createNetServer((socket: Socket) => {\n // 使用计数器生成唯一 ID,而不是依赖 remoteAddress/port(Windows 命名管道可能返回 undefined)\n const clientId = `client-${++connectionCounter}-${Date.now()}`;\n this.udsClients.set(clientId, socket);\n\n console.log(`[Viewer Worker] 新的 UDS 客户端连接: ${clientId}, 当前连接数: ${this.udsClients.size}`);\n\n let buffer = '';\n socket.on('data', (data: Buffer) => {\n buffer += data.toString();\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const msg: DebugHubIPCMessage = JSON.parse(line);\n this.handleUDSMessage(msg, socket, clientId);\n } catch (err) {\n console.error('[Viewer Worker] UDS 消息解析失败:', err);\n }\n }\n });\n\n socket.on('close', () => {\n this.udsClients.delete(clientId);\n console.log(`[Viewer Worker] UDS 客户端断开: ${clientId}, 当前连接数: ${this.udsClients.size}`);\n });\n\n socket.on('error', (err) => {\n console.error('[Viewer Worker] UDS 客户端错误:', err);\n this.udsClients.delete(clientId);\n });\n });\n\n // 添加错误处理\n this.udsServer.on('error', (err: Error) => {\n console.error(`[Viewer Worker] UDS 服务器错误: ${err.message}`);\n });\n\n this.udsServer.listen(this.udsPath, () => {\n console.log(`[Viewer Worker] UDS 服务器已启动: ${this.udsPath}`);\n });\n }\n\n /**\n * 处理 UDS 消息(复用现有处理方法)\n */\n private handleUDSMessage(msg: DebugHubIPCMessage, socket: Socket, clientId: string): void {\n switch (msg.type) {\n case 'register-agent':\n this.handleRegisterAgent(msg, clientId);\n break;\n case 'update-agent-inspector':\n this.handleUpdateAgentInspector(msg);\n break;\n case 'update-agent-overview':\n this.handleUpdateAgentOverview(msg);\n break;\n case 'push-messages':\n this.handlePushMessages(msg);\n break;\n case 'register-tools':\n this.handleRegisterTools(msg);\n break;\n case 'set-current-agent':\n this.handleSetCurrentAgent(msg);\n // 发送确认\n socket.write(JSON.stringify({ type: 'agent-switched', agentId: msg.agentId }) + '\\n');\n break;\n case 'unregister-agent':\n this.handleUnregisterAgent(msg);\n break;\n case 'push-notification':\n this.handlePushNotification(msg);\n break;\n case 'request-input':\n this.handleRequestInput(msg);\n break;\n case 'consume-queued-input':\n this.handleConsumeQueuedInput(msg.agentId, msg.inputId);\n break;\n case 'stop':\n this.handleStop();\n break;\n }\n }\n\n // ========== HTTP 请求处理 ==========\n\n private handleRequest(req: IncomingMessage, res: ServerResponse) {\n res.setHeader('Access-Control-Allow-Origin', '*');\n\n // 路由分发\n const urlObj = new URL(req.url || '/', 'http://localhost');\n const url = urlObj.pathname;\n\n // 主页\n if (url === '/' || url === '/index.html') {\n this.handleIndex(req, res);\n return;\n }\n\n // API 端点\n if (url.startsWith('/api/')) {\n this.handleAPI(req, res);\n return;\n }\n\n if (url === '/mcp' || url === '/mcp/') {\n void this.handleMCP(req, res);\n return;\n }\n\n // 静态文件:工具渲染模板\n if (url.startsWith('/tools/')) {\n this.handleStaticToolFile(req, res, url);\n return;\n }\n\n // 统一的 Feature 模板路由(新格式:/template/{packageName}/{templateName}.render.js)\n if (url.startsWith('/template/')) {\n this.handleUnifiedTemplate(req, res, url);\n return;\n }\n\n // Feature 工具渲染模板(旧格式,向后兼容)\n if (url.startsWith('/features/')) {\n this.handleFeatureTemplate(req, res, url);\n return;\n }\n\n // npm 包中的 Feature 模板(旧格式,向后兼容)\n if (url.startsWith('/npm/')) {\n this.handleNpmFeatureTemplate(req, res, url);\n return;\n }\n\n // 静态资源:chunk 文件和其他 JS/CSS 文件\n if (/^\\/(chunk-|BasicAgent-|ExplorerAgent-|notification-|resolver-|types-|index\\.js).*$/.test(url)) {\n this.handleStaticAsset(req, res, url);\n return;\n }\n\n res.writeHead(404);\n res.end('Not Found');\n }\n\n /**\n * 主页 - 带多 Agent 切换器\n */\n private handleIndex(req: IncomingMessage, res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(this.getHtml());\n }\n\n /**\n * API 端点路由\n */\n private handleAPI(req: IncomingMessage, res: ServerResponse): void {\n const urlObj = new URL(req.url || '/', 'http://localhost');\n const url = urlObj.pathname;\n\n // GET /api/agents - Agent 列表\n if (url === '/api/agents' && req.method === 'GET') {\n this.handleGetAgents(req, res);\n return;\n }\n\n // GET /api/agents/current - 当前 Agent\n if (url === '/api/agents/current' && req.method === 'GET') {\n this.handleGetCurrentAgent(req, res);\n return;\n }\n\n // PUT /api/agents/current - 切换当前 Agent\n if (url === '/api/agents/current' && req.method === 'PUT') {\n this.handleSetCurrentAgentHttp(req, res);\n return;\n }\n\n // GET /api/templates/feature - 获取 Feature 模板映射\n if (url === '/api/templates/feature' && req.method === 'GET') {\n this.handleGetFeatureTemplates(req, res, urlObj.searchParams);\n return;\n }\n\n if (url === '/api/logs' && req.method === 'GET') {\n this.handleGetLogs(req, res, urlObj.searchParams);\n return;\n }\n\n if (url === '/api/mcp-info' && req.method === 'GET') {\n this.handleGetMCPInfo(req, res);\n return;\n }\n\n // GET /api/agents/:id/messages - 指定 Agent 的消息\n const msgMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/messages$/);\n if (msgMatch && req.method === 'GET') {\n this.handleGetAgentMessages(req, res, msgMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/tools - 指定 Agent 的工具\n const toolsMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/tools$/);\n if (toolsMatch && req.method === 'GET') {\n this.handleGetAgentTools(req, res, toolsMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/hooks - 指定 Agent 的 hook 监视快照\n const hooksMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/hooks$/);\n if (hooksMatch && req.method === 'GET') {\n this.handleGetAgentHooks(req, res, hooksMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/overview - 指定 Agent 的概览统计\n const overviewMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/overview$/);\n if (overviewMatch && req.method === 'GET') {\n this.handleGetAgentOverview(req, res, overviewMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/notification - 指定 Agent 的通知状态\n const notifMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/notification$/);\n if (notifMatch && req.method === 'GET') {\n this.handleGetAgentNotification(req, res, notifMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/connection - 指定 Agent 的真实连接状态\n const connectionMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/connection$/);\n if (connectionMatch && req.method === 'GET') {\n this.handleGetAgentConnection(req, res, connectionMatch[1]);\n return;\n }\n\n // DELETE /api/agents/:id - 删除已断开的 Agent 会话\n const deleteAgentMatch = url.match(/^\\/api\\/agents\\/([^/]+)$/);\n if (deleteAgentMatch && req.method === 'DELETE') {\n this.handleDeleteAgent(req, res, deleteAgentMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/input-requests - 获取输入请求列表\n const inputReqMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/input-requests$/);\n if (inputReqMatch && req.method === 'GET') {\n this.handleGetInputRequests(req, res, inputReqMatch[1]);\n return;\n }\n\n // POST /api/agents/:id/input - 提交用户输入\n const inputPostMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/input$/);\n if (inputPostMatch && req.method === 'POST') {\n this.handlePostInput(req, res, inputPostMatch[1]);\n return;\n }\n\n // POST /api/agents/:id/queue-input - 运行期间排队用户输入\n const queueInputMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/queue-input$/);\n if (queueInputMatch && req.method === 'POST') {\n this.handleQueueInput(req, res, queueInputMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/queued-inputs - 获取排队中的用户输入\n const queuedInputsMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/queued-inputs$/);\n if (queuedInputsMatch && req.method === 'GET') {\n this.handleGetQueuedInputs(req, res, queuedInputsMatch[1]);\n return;\n }\n\n // POST /api/agents/:id/dequeue-input - 消费第一条排队消息\n const dequeueMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/dequeue-input$/);\n if (dequeueMatch && req.method === 'POST') {\n this.handleDequeueInput(req, res, dequeueMatch[1]);\n return;\n }\n\n // POST /api/agents/:id/interrupt - 中断正在运行的 Agent\n const interruptMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/interrupt$/);\n if (interruptMatch && req.method === 'POST') {\n this.handleInterrupt(req, res, interruptMatch[1]);\n return;\n }\n\n // GET /api/agents/:id/running - 查询 Agent 是否正在运行\n const runningMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/running$/);\n if (runningMatch && req.method === 'GET') {\n this.handleGetRunning(req, res, runningMatch[1]);\n return;\n }\n\n // 兼容端点:/api/messages → 当前 Agent 的消息\n if (url === '/api/messages' && req.method === 'GET') {\n if (this.currentAgentId) {\n this.handleGetAgentMessages(req, res, this.currentAgentId);\n } else {\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify([]));\n }\n return;\n }\n\n // 兼容端点:/api/tools → 当前 Agent 的工具\n if (url === '/api/tools' && req.method === 'GET') {\n if (this.currentAgentId) {\n this.handleGetAgentTools(req, res, this.currentAgentId);\n } else {\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify([]));\n }\n return;\n }\n\n res.writeHead(404);\n res.end('API Not Found');\n }\n\n // ========== API 处理器 ==========\n\n /**\n * GET /api/agents - 获取所有 Agent\n */\n private handleGetAgents(req: IncomingMessage, res: ServerResponse): void {\n const agents = Array.from(this.agentSessions.values()).map(session => ({\n id: session.id,\n name: session.name,\n createdAt: session.createdAt,\n messageCount: session.messages.length,\n connected: this.isSessionConnected(session),\n }));\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n agents,\n currentAgentId: this.currentAgentId,\n }));\n }\n\n /**\n * GET /api/agents/current - 获取当前 Agent\n */\n private handleGetCurrentAgent(req: IncomingMessage, res: ServerResponse): void {\n if (!this.currentAgentId) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'No current agent' }));\n return;\n }\n\n const session = this.agentSessions.get(this.currentAgentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n id: session.id,\n name: session.name,\n createdAt: session.createdAt,\n }));\n }\n\n /**\n * PUT /api/agents/current - 切换当前 Agent\n */\n public handleSetCurrentAgentHttp(req: IncomingMessage, res: ServerResponse): void {\n let body = '';\n req.on('data', chunk => { body += chunk; });\n req.on('end', () => {\n try {\n const data = JSON.parse(body);\n const agentId = data.agentId;\n\n if (!this.agentSessions.has(agentId)) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n this.currentAgentId = agentId;\n this.updateSessionActivity(agentId);\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ success: true, agentId }));\n\n // 通知主进程\n if (process.send) {\n process.send({ type: 'agent-switched', agentId });\n }\n } catch (e) {\n res.writeHead(400);\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n }\n\n /**\n * GET /api/templates/feature - 获取 Feature 模板映射\n */\n public handleGetFeatureTemplates(req: IncomingMessage, res: ServerResponse, searchParams?: URLSearchParams): void {\n // 确定目标 agentId:优先用 query 参数,其次用 currentAgentId\n const targetAgentId = searchParams?.get('agentId') || this.currentAgentId;\n\n // 获取目标 Agent 的项目根目录和模板映射\n const session = targetAgentId ? this.agentSessions.get(targetAgentId) : undefined;\n const projectRoot = session?.projectRoot;\n const templates = targetAgentId ? this.featureTemplateMap.get(targetAgentId) : undefined;\n\n if (!templates || Object.keys(templates).length === 0) {\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end('{}');\n return;\n }\n\n // 将绝对路径转换为 HTTP URL\n const featureTemplateMapForFrontend: Record<string, string> = {};\n for (const [templateName, absolutePath] of Object.entries(templates)) {\n const normalizedPath = absolutePath.replace(/\\\\/g, '/');\n const url = this.templatePathToUrl(normalizedPath, projectRoot);\n if (url) {\n featureTemplateMapForFrontend[templateName] = url;\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(featureTemplateMapForFrontend));\n }\n\n /**\n * 将模板文件路径转换为 HTTP URL\n * 支持多种来源:\n * 1. 独立 npm 包 node_modules/@scope/package/dist/templates/xxx.render.js\n * 2. npm 包 node_modules/agentdev/dist/features/xxx/templates/xxx.render.js\n * 3. npm workspace 符号链接(路径是真实物理路径,但需要转换为 node_modules 路径)\n * 4. 项目内 dist/features/xxx/templates/xxx.render.js\n * 5. 项目内 src/features/xxx/templates/xxx.render.ts\n * 6. 用户自定义路径\n */\n private templatePathToUrl(normalizedPath: string, projectRoot?: string): string | null {\n // 如果已经是 URL 格式,直接返回\n if (normalizedPath.startsWith('/template/') || \n normalizedPath.startsWith('/features/') || \n normalizedPath.startsWith('/npm/') ||\n normalizedPath.startsWith('/tools/')) {\n return normalizedPath;\n }\n\n // 规范化路径:统一使用 / 分隔符\n let normalizedForMatch = normalizedPath.replace(/\\\\/g, '/');\n \n // 处理 Windows 盘符路径\n // 格式可能是: /D:/code/... 或 D:/code/...\n const windowsDriveMatch = normalizedForMatch.match(/^(?:\\/)?([A-Za-z]):\\/(.+)$/);\n if (windowsDriveMatch) {\n normalizedForMatch = windowsDriveMatch[2]; // 只保留盘符后的路径部分\n }\n \n // 如果有项目根目录,提取相对于项目根目录的路径\n if (projectRoot) {\n const normalizedProjectRoot = projectRoot.replace(/\\\\/g, '/');\n // 尝试匹配项目根目录(可能包含盘符)\n const projectRootMatch = normalizedProjectRoot.match(/^(?:\\/)?([A-Za-z]):\\/(.+)$/);\n if (projectRootMatch) {\n const projectRootPath = projectRootMatch[2]; // 移除盘符后的项目根目录\n if (normalizedForMatch.startsWith(projectRootPath + '/')) {\n normalizedForMatch = normalizedForMatch.substring(projectRootPath.length + 1);\n }\n } else if (normalizedForMatch.startsWith(normalizedProjectRoot + '/')) {\n normalizedForMatch = normalizedForMatch.substring(normalizedProjectRoot.length + 1);\n }\n }\n\n // 模式 1: 独立 npm 包路径 node_modules/@scope/package/dist/templates/xxx.render.js\n // 例如: node_modules/@agentdev/shell-feature/dist/templates/bash.render.js\n let match = normalizedForMatch.match(/node_modules\\/(@[^/]+\\/[^/]+)\\/dist\\/templates\\/(.+\\.render\\.js)$/);\n if (match) {\n const [, scopedPackageName, templateFile] = match;\n return `/npm/${scopedPackageName}/templates/${templateFile}`;\n }\n\n // 模式 1b: 独立 npm 包路径(无 scope)node_modules/package/dist/templates/xxx.render.js\n match = normalizedForMatch.match(/node_modules\\/([^/@][^/]*)\\/dist\\/templates\\/(.+\\.render\\.js)$/);\n if (match) {\n const [, packageName, templateFile] = match;\n return `/npm/${packageName}/templates/${templateFile}`;\n }\n\n // 模式 2: npm 包路径 node_modules/xxx/dist/features/xxx/templates/xxx.render.js\n match = normalizedForMatch.match(/node_modules\\/([^/]+)\\/dist\\/features\\/([^/]+)\\/templates\\/(.+\\.render\\.js)$/);\n if (match) {\n const [, packageName, featureName, templateFile] = match;\n return `/npm/${packageName}/features/${featureName}/${templateFile}`;\n }\n\n // 模式 3: npm workspace 符号链接(检查路径是否来自 node_modules 中的符号链接目标)\n // 例如:D:/code/AgentDev/dist/features/... 可能来自 D:/code/Project/node_modules/agentdev -> D:/code/AgentDev\n if (projectRoot) {\n const npmPackageUrl = this.resolveNpmWorkspacePackage(normalizedPath, projectRoot);\n if (npmPackageUrl) {\n return npmPackageUrl;\n }\n }\n\n // 模式 4: 项目内 dist 路径(支持 Windows 盘符前缀)\n match = normalizedForMatch.match(/dist\\/features\\/([^/]+)\\/templates\\/(.+\\.render\\.js)$/);\n if (match) {\n const [, featureName, templateFile] = match;\n return `/features/${featureName}/${templateFile}`;\n }\n\n // 模式 4b: packages 目录下的独立包路径 packages/xxx/dist/templates/xxx.render.js\n // 例如: packages/shell-feature/dist/templates/bash.render.js\n match = normalizedForMatch.match(/packages\\/([^/]+)\\/dist\\/templates\\/(.+\\.render\\.js)$/);\n if (match) {\n const [, packageName, templateFile] = match;\n // 将包名转换为 npm 包格式,假设是 @agentdev scope\n return `/npm/@agentdev/${packageName}/templates/${templateFile}`;\n }\n\n // 模式 5: 项目内 src 路径(支持 Windows 盘符前缀)\n match = normalizedForMatch.match(/src\\/features\\/([^/]+)\\/templates\\/(.+\\.render\\.(ts|js))$/);\n if (match) {\n const [, featureName, templateFile] = match;\n return `/features/${featureName}/${templateFile}`;\n }\n\n // 无法匹配的路径,记录警告\n console.warn(`[Viewer Worker] 无法解析模板路径: ${normalizedPath}`);\n return null;\n }\n\n /**\n * 解析 npm workspace 符号链接\n * 当 Feature 来自 npm workspace 时,import.meta.url 返回的是符号链接的真实物理路径\n * 需要检查 node_modules 目录中的符号链接来确定包名\n */\n private npmWorkspaceCache: Map<string, string> = new Map();\n\n private resolveNpmWorkspacePackage(normalizedPath: string, projectRoot: string): string | null {\n // 从路径中提取可能的包根目录(包含 dist/features 的目录)\n const featuresMatch = normalizedPath.match(/^(.+)\\/dist\\/features\\/([^/]+)\\/templates\\/(.+\\.render\\.js)$/);\n if (!featuresMatch) {\n return null;\n }\n\n const [, packageRoot, featureName, templateFile] = featuresMatch;\n const normalizedProjectRoot = projectRoot.replace(/\\\\/g, '/');\n\n // 检查缓存\n const cacheKey = `${normalizedProjectRoot}:${packageRoot}`;\n if (this.npmWorkspaceCache.has(cacheKey)) {\n const packageName = this.npmWorkspaceCache.get(cacheKey)!;\n return `/npm/${packageName}/features/${featureName}/${templateFile}`;\n }\n\n // 扫描 node_modules 目录查找符号链接\n const nodeModulesPath = join(normalizedProjectRoot, 'node_modules');\n try {\n const fs = require('fs');\n if (!fs.existsSync(nodeModulesPath)) {\n return null;\n }\n\n const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isSymbolicLink()) {\n const linkPath = join(nodeModulesPath, entry.name);\n try {\n // 解析符号链接的真实路径\n const realPath = fs.realpathSync(linkPath).replace(/\\\\/g, '/');\n \n // 检查包根目录是否匹配\n if (realPath === packageRoot || realPath + '/dist' === packageRoot) {\n // 找到匹配的包!缓存结果\n this.npmWorkspaceCache.set(cacheKey, entry.name);\n console.log(`[Viewer Worker] 解析 npm workspace 符号链接: ${entry.name} -> ${realPath}`);\n return `/npm/${entry.name}/features/${featureName}/${templateFile}`;\n }\n } catch {\n // 忽略无法解析的符号链接\n }\n }\n }\n } catch (e) {\n // 忽略扫描错误\n }\n\n return null;\n }\n\n /**\n * GET /api/agents/:id/messages - 获取指定 Agent 的消息\n */\n private handleGetAgentMessages(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n agentId,\n messages: session.messages,\n }));\n }\n\n /**\n * GET /api/agents/:id/tools - 获取指定 Agent 的工具\n */\n private handleGetAgentTools(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(session.tools));\n }\n\n private handleGetAgentHooks(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(session.hookInspector || {\n lifecycleOrder: [],\n features: [],\n hooks: [],\n }));\n }\n\n private handleGetAgentOverview(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(this.getMergedOverview(session)));\n }\n\n /**\n * GET /api/agents/:id/notification - 获取指定 Agent 的通知状态\n */\n private handleGetAgentNotification(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n const hasNewEvents = session.events.length > session.lastEventCount;\n session.lastEventCount = session.events.length;\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n state: session.currentState,\n event: session.events.length > 0 ? session.events[session.events.length - 1] : null,\n runtime: this.cloneRuntimeState(this.getSessionRuntimeState(session)),\n callActive: session.callActive === true,\n hasNewEvents,\n }));\n }\n\n private handleGetLogs(req: IncomingMessage, res: ServerResponse, searchParams: URLSearchParams): void {\n const result = this.queryLogs({\n scope: searchParams.get('scope') === 'all' ? 'all' : 'current',\n agentId: searchParams.get('agentId'),\n level: searchParams.get('level') || undefined,\n namespace: searchParams.get('namespace') || undefined,\n feature: searchParams.get('feature') || undefined,\n lifecycle: searchParams.get('lifecycle') || undefined,\n from: this.parseNumberParam(searchParams.get('from')),\n to: this.parseNumberParam(searchParams.get('to')),\n limit: this.parseNumberParam(searchParams.get('limit')),\n offset: this.parseNumberParam(searchParams.get('offset')),\n search: searchParams.get('search') || undefined,\n });\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(result));\n }\n\n private handleGetMCPInfo(req: IncomingMessage, res: ServerResponse): void {\n const host = req.headers.host || `localhost:${this.port}`;\n const origin = `http://${host}`;\n const endpoint = `${origin}/mcp`;\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n enabled: true,\n endpoint,\n transport: 'Streamable HTTP',\n version: 'read-only debugger facade',\n commands: {\n claudeDesktop: {\n json: {\n mcpServers: {\n agentdevDebugger: {\n type: 'http',\n url: endpoint,\n },\n },\n },\n },\n codex: {\n json: {\n servers: {\n agentdevDebugger: {\n type: 'http',\n url: endpoint,\n },\n },\n },\n },\n curlInitialize: `curl -X POST ${endpoint} -H \"Content-Type: application/json\" -d \"{\\\\\"jsonrpc\\\\\":\\\\\"2.0\\\\\",\\\\\"id\\\\\":1,\\\\\"method\\\\\":\\\\\"initialize\\\\\",\\\\\"params\\\\\":{\\\\\"protocolVersion\\\\\":\\\\\"2025-03-26\\\\\",\\\\\"capabilities\\\\\":{},\\\\\"clientInfo\\\\\":{\\\\\"name\\\\\":\\\\\"manual-client\\\\\",\\\\\"version\\\\\":\\\\\"1.0.0\\\\\"}}}\"`,\n },\n tools: DEBUGGER_MCP_TOOL_DEFINITIONS,\n resources: DEBUGGER_MCP_RESOURCE_DEFINITIONS,\n prompts: DEBUGGER_MCP_PROMPT_DEFINITIONS,\n }));\n }\n\n /**\n * GET /api/agents/:id/connection - 获取指定 Agent 的真实连接状态\n */\n private handleGetAgentConnection(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n const connected = this.isSessionConnected(session);\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ connected }));\n }\n\n /**\n * DELETE /api/agents/:id - 删除已断开的 Agent 会话\n */\n private handleDeleteAgent(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n if (this.isSessionConnected(session)) {\n res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Connected agent cannot be deleted' }));\n return;\n }\n\n this.agentSessions.delete(agentId);\n console.log(`[Viewer Worker] 已删除断开的 Agent 会话: ${agentId}`);\n\n if (this.currentAgentId === agentId) {\n const remaining = Array.from(this.agentSessions.values());\n const nextActive = remaining.find(candidate => this.isSessionConnected(candidate)) || remaining[0];\n this.currentAgentId = nextActive?.id || null;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n success: true,\n agentId,\n currentAgentId: this.currentAgentId,\n }));\n }\n\n /**\n * 获取输入请求列表\n */\n private handleGetInputRequests(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n const pendingRequests = ((session as any).pendingInputRequests as Map<string, any>) || new Map();\n const requests = Array.from(pendingRequests.entries()).map(([requestId, data]) => ({\n requestId,\n prompt: data.prompt,\n placeholder: data.placeholder,\n initialValue: data.initialValue,\n actions: data.actions,\n mode: data.mode,\n questions: data.questions,\n timestamp: data.timestamp,\n }));\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(requests));\n }\n\n /**\n * 提交用户输入\n */\n private handlePostInput(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n let body = '';\n req.on('data', (chunk) => { body += chunk; });\n req.on('end', () => {\n try {\n const { requestId, input, response } = JSON.parse(body);\n\n const session = this.agentSessions.get(agentId);\n const pendingRequests = (session as any).pendingInputRequests as Map<string, any> | undefined;\n if (!session || !pendingRequests?.has(requestId)) {\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Request not found or expired' }));\n return;\n }\n\n const normalizedResponse = response ?? {\n kind: 'text',\n text: input,\n };\n\n // 移除请求\n pendingRequests.delete(requestId);\n\n // 通过 UDS 发送响应到正确的客户端\n const targetClientId = session.clientId;\n if (targetClientId) {\n const targetSocket = this.udsClients.get(targetClientId);\n if (targetSocket) {\n try {\n targetSocket.write(JSON.stringify({\n type: 'input-response',\n agentId,\n requestId,\n input: normalizedResponse.text ?? input ?? '',\n response: normalizedResponse,\n }) + '\\n');\n console.log(`[Viewer Worker] 输入响应已发送到 ${targetClientId}: ${requestId}`);\n } catch (writeError) {\n console.error('[Viewer Worker] UDS 写入失败:', writeError);\n }\n } else {\n console.warn(`[Viewer Worker] 目标客户端连接不存在: ${targetClientId}`);\n }\n } else {\n // 向后兼容:如果没有记录 clientId,广播到所有客户端\n console.warn('[Viewer Worker] Agent 未记录 clientId,尝试广播到所有客户端');\n for (const [cid, socket] of this.udsClients) {\n try {\n socket.write(JSON.stringify({\n type: 'input-response',\n agentId,\n requestId,\n input: normalizedResponse.text ?? input ?? '',\n response: normalizedResponse,\n }) + '\\n');\n console.log(`[Viewer Worker] 输入响应广播到 ${cid}`);\n } catch (writeError) {\n console.error(`[Viewer Worker] 向 ${cid} 广播失败:`, writeError);\n }\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ success: true }));\n } catch (e) {\n res.writeHead(400);\n res.end(JSON.stringify({ error: 'Invalid request' }));\n }\n });\n }\n\n /**\n * 排队用户输入(运行期间提交的消息)\n */\n private handleQueueInput(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n let body = '';\n req.on('data', (chunk) => { body += chunk; });\n req.on('end', () => {\n try {\n const { text } = JSON.parse(body);\n if (!text || typeof text !== 'string') {\n res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Missing or invalid text' }));\n return;\n }\n\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n if (!session.queuedInputs) {\n (session as any).queuedInputs = [];\n }\n\n const queuedInput = {\n id: `q-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n text,\n timestamp: Date.now(),\n };\n session.queuedInputs.push(queuedInput);\n console.log(`[Viewer Worker] 用户输入已排队: ${agentId}, queueLength=${session.queuedInputs.length}`);\n\n const targetClientId = session.clientId;\n const targetSocket = targetClientId ? this.udsClients.get(targetClientId) : null;\n (session as any).useArbiterQueue = !!targetSocket;\n if (targetSocket) {\n try {\n targetSocket.write(JSON.stringify({\n type: 'queue-input',\n agentId,\n input: queuedInput,\n }) + '\\n');\n } catch (writeError) {\n console.error('[Viewer Worker] queue-input 转发到运行时失败:', writeError);\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ success: true, id: queuedInput.id, queueLength: session.queuedInputs.length }));\n } catch (e) {\n res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Invalid request' }));\n }\n });\n }\n\n /**\n * 获取排队中的用户输入\n */\n private handleGetQueuedInputs(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n const queued = session.queuedInputs || [];\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(queued));\n }\n\n /**\n * 消费第一条排队消息\n */\n private handleDequeueInput(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n if ((session as any).useArbiterQueue) {\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ input: null, remaining: session.queuedInputs?.length || 0 }));\n return;\n }\n\n const queued = session.queuedInputs || [];\n if (queued.length === 0) {\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ input: null }));\n return;\n }\n\n const input = queued.shift()!;\n console.log(`[Viewer Worker] 消费排队输入: ${agentId}, remaining=${queued.length}`);\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ input, remaining: queued.length }));\n }\n\n private handleConsumeQueuedInput(agentId: string, inputId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session || !Array.isArray(session.queuedInputs) || !inputId) {\n return;\n }\n const nextQueue = session.queuedInputs.filter((item) => item?.id !== inputId);\n if (nextQueue.length !== session.queuedInputs.length) {\n session.queuedInputs = nextQueue;\n console.log(`[Viewer Worker] 已移除排队输入: ${agentId}, inputId=${inputId}, remaining=${session.queuedInputs.length}`);\n }\n }\n\n /**\n * 中断正在运行的 Agent\n */\n private handleInterrupt(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n console.log(`[VW.handleInterrupt] agentId=${agentId}, sessionFound=${!!session}, sessionKeys=[${[...this.agentSessions.keys()].join(',')}]`);\n if (!session) {\n console.log(`[VW.handleInterrupt] 404 - agent not found in sessions`);\n res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n // 通过 UDS 发送中断消息到 Agent 进程\n const targetClientId = session.clientId;\n console.log(`[VW.handleInterrupt] session.clientId=${targetClientId}, udsClientKeys=[${[...this.udsClients.keys()].join(',')}]`);\n if (targetClientId) {\n const targetSocket = this.udsClients.get(targetClientId);\n console.log(`[VW.handleInterrupt] socketFound=${!!targetSocket}, socketDestroyed=${targetSocket?.destroyed}`);\n if (targetSocket) {\n try {\n if (Array.isArray(session.queuedInputs) && session.queuedInputs.length > 0) {\n session.queuedInputs = [];\n }\n targetSocket.write(JSON.stringify({\n type: 'interrupt-agent',\n agentId,\n clearQueue: true,\n }) + '\\n');\n console.log(`[VW.handleInterrupt] UDS message sent to ${targetClientId}: ${agentId}`);\n } catch (writeError) {\n console.error('[VW.handleInterrupt] UDS write failed:', writeError);\n }\n } else {\n console.warn(`[VW.handleInterrupt] no UDS socket for clientId=${targetClientId}`);\n }\n } else {\n console.warn(`[VW.handleInterrupt] session has no clientId`);\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ success: true }));\n }\n\n /**\n * 查询 Agent 是否正在运行\n */\n private handleGetRunning(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (!session) {\n res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'Agent not found' }));\n return;\n }\n\n // 通过 UDS 查询运行状态\n const targetClientId = session.clientId;\n if (targetClientId) {\n const targetSocket = this.udsClients.get(targetClientId);\n if (targetSocket) {\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ running: true }));\n return;\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ running: false }));\n }\n\n // ========== 会话管理 ==========\n\n /**\n * 获取或创建会话\n */\n public getOrCreateSession(agentId: string, name: string): AgentSession {\n let session = this.agentSessions.get(agentId);\n if (!session) {\n session = {\n id: agentId,\n name,\n messages: [],\n tools: [],\n createdAt: Date.now(),\n lastActive: Date.now(),\n // 通知系统扩展\n currentState: null,\n callActive: false,\n runtimeState: this.createEmptyRuntimeState(),\n events: [],\n lastEventCount: 0,\n logs: [],\n overview: this.createEmptyOverview(),\n queuedInputs: [],\n };\n this.agentSessions.set(agentId, session);\n }\n return session;\n }\n\n /**\n * 更新会话活跃时间\n */\n public updateSessionActivity(agentId: string): void {\n const session = this.agentSessions.get(agentId);\n if (session) {\n session.lastActive = Date.now();\n }\n }\n\n private isSessionConnected(session: AgentSession): boolean {\n return !!session.clientId && this.udsClients.has(session.clientId);\n }\n\n /**\n * 应用内存限制\n */\n public enforceMemoryLimits(session: AgentSession): void {\n // 消息数量限制\n while (session.messages.length > this.MAX_MESSAGES) {\n session.messages.shift();\n }\n\n // 字节限制\n let byteSize = 0;\n for (let i = 0; i < session.messages.length; i++) {\n byteSize += JSON.stringify(session.messages[i]).length;\n if (byteSize > this.MAX_BYTES) {\n // 删除超出部分\n session.messages = session.messages.slice(i + 1);\n break;\n }\n }\n }\n\n // ========== IPC 消息处理 ==========\n\n /**\n * 处理注册 Agent\n */\n public handleRegisterAgent(msg: any, clientId?: string): void {\n const { agentId, name, createdAt, projectRoot, featureTemplates, hookInspector, overview, activeInputRequest } = msg;\n const session = this.getOrCreateSession(agentId, name);\n\n // 存储项目根目录(用于模板文件加载)\n if (projectRoot) {\n session.projectRoot = projectRoot;\n }\n\n // 记录所属客户端连接(用于多进程输入响应路由)\n if (clientId) {\n session.clientId = clientId;\n }\n\n // 收集 Feature 模板路径(按 agent 隔离)\n if (featureTemplates && typeof featureTemplates === 'object') {\n this.featureTemplateMap.set(agentId, featureTemplates);\n }\n\n if (hookInspector) {\n session.hookInspector = hookInspector;\n }\n if (overview) {\n session.overview = overview;\n }\n\n // 恢复活跃的输入请求(用于重连后恢复输入框)\n if (activeInputRequest) {\n let pendingRequests = (session as any).pendingInputRequests as Map<string, any>;\n if (!pendingRequests) {\n pendingRequests = new Map();\n (session as any).pendingInputRequests = pendingRequests;\n }\n\n // 重新存储请求\n pendingRequests.set(activeInputRequest.requestId, {\n prompt: activeInputRequest.prompt,\n placeholder: activeInputRequest.placeholder,\n initialValue: activeInputRequest.initialValue,\n actions: activeInputRequest.actions,\n timestamp: activeInputRequest.timestamp,\n });\n\n // 如果有活跃输入请求,自动切换到该 Agent(确保前端能看到输入框)\n this.currentAgentId = agentId;\n\n console.log(`[Viewer Worker] 恢复活跃输入请求: ${activeInputRequest.requestId},切换到 Agent: ${agentId}`);\n }\n\n // 首个 Agent 自动成为当前(如果没有活跃输入请求的情况)\n if (this.agentSessions.size === 1 && !activeInputRequest) {\n this.currentAgentId = agentId;\n }\n\n console.log(`[Viewer Worker] Agent 已注册: ${agentId} (${name})${clientId ? ` [client: ${clientId}]` : ''}`);\n }\n\n public handleUpdateAgentInspector(msg: any): void {\n const { agentId, hookInspector } = msg;\n const session = this.agentSessions.get(agentId);\n if (!session) return;\n session.hookInspector = hookInspector;\n this.updateSessionActivity(agentId);\n }\n\n public handleUpdateAgentOverview(msg: { agentId: string; overview: AgentOverviewSnapshot }): void {\n const { agentId, overview } = msg;\n const session = this.agentSessions.get(agentId);\n if (!session) return;\n session.overview = {\n ...overview,\n runtime: this.cloneRuntimeState(session.runtimeState || overview.runtime || this.createEmptyRuntimeState()),\n };\n this.updateSessionActivity(agentId);\n }\n\n /**\n * 清空 Feature 模板映射(当 Agent 断开连接时调用)\n */\n private clearFeatureTemplates(agentId: string): void {\n this.featureTemplateMap.delete(agentId);\n }\n\n /**\n * 处理推送消息(带去重优化)\n *\n * 只有在消息真正变化时才更新会话并触发前端更新\n */\n public handlePushMessages(msg: any): void {\n const { agentId, messages } = msg;\n const session = this.agentSessions.get(agentId);\n if (!session) return;\n\n // 消息变化检测\n const messagesChanged = this.hasMessagesChanged(session, messages);\n\n // 只有消息真正变化时才更新\n if (messagesChanged) {\n session.messages = messages;\n // 更新最后一条消息的签名,用于下次比较\n session._lastMessageSig = this.getLastMessageSignature(messages);\n this.updateSessionActivity(agentId);\n this.enforceMemoryLimits(session);\n }\n }\n\n private createEmptyOverview(): AgentOverviewSnapshot {\n return {\n updatedAt: 0,\n context: {\n messageCount: 0,\n charCount: 0,\n toolCallCount: 0,\n turnCount: 0,\n },\n usageStats: {\n totalUsage: {\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n },\n calls: [],\n totalRequests: 0,\n totalCacheHitRequests: 0,\n },\n runtime: this.createEmptyRuntimeState(),\n };\n }\n\n private createEmptyRuntimeState(): AgentRuntimeSnapshot {\n return {\n stage: 'idle',\n callActive: false,\n charCount: 0,\n thinkingChars: 0,\n contentChars: 0,\n toolCallCount: 0,\n activeToolNames: [],\n activeToolCount: 0,\n callStartedAt: 0,\n stageStartedAt: 0,\n updatedAt: 0,\n lastErrorType: null,\n lastErrorMessage: null,\n };\n }\n\n private cloneRuntimeState(snapshot?: AgentRuntimeSnapshot | null): AgentRuntimeSnapshot {\n const source = snapshot || this.createEmptyRuntimeState();\n return {\n ...source,\n activeToolNames: Array.isArray(source.activeToolNames) ? source.activeToolNames.slice() : [],\n };\n }\n\n private getRuntimeStageFromLLMPhase(phase: string): AgentRuntimeSnapshot['stage'] {\n if (phase === 'thinking') return 'llm_thinking';\n if (phase === 'content') return 'llm_content';\n if (phase === 'tool_calling') return 'llm_tool_call_building';\n return 'awaiting_runtime';\n }\n\n private updateRuntimeStage(\n runtimeState: AgentRuntimeSnapshot,\n nextStage: AgentRuntimeSnapshot['stage'],\n timestamp: number,\n ): AgentRuntimeSnapshot {\n const nextTimestamp = timestamp || Date.now();\n return {\n ...runtimeState,\n stage: nextStage,\n stageStartedAt: runtimeState.stage === nextStage\n ? (runtimeState.stageStartedAt || nextTimestamp)\n : nextTimestamp,\n updatedAt: nextTimestamp,\n };\n }\n\n private getSessionRuntimeState(session: AgentSession): AgentRuntimeSnapshot {\n if (!session.runtimeState) {\n session.runtimeState = this.createEmptyRuntimeState();\n }\n return session.runtimeState;\n }\n\n private getMergedOverview(session: AgentSession): AgentOverviewSnapshot {\n const base = session.overview || this.createEmptyOverview();\n return {\n ...base,\n runtime: this.cloneRuntimeState(this.getSessionRuntimeState(session)),\n };\n }\n\n /**\n * 检查消息是否发生变化\n */\n private hasMessagesChanged(session: AgentSession, newMessages: any[]): boolean {\n // 快速检查:消息数量\n if (newMessages.length !== session.messages.length) {\n return true;\n }\n\n // 如果没有消息,直接返回 false\n if (newMessages.length === 0) {\n return false;\n }\n\n // 比较最后一条消息的签名(新消息总是追加到末尾)\n const newSig = this.getLastMessageSignature(newMessages);\n const oldSig = (session as any)._lastMessageSig;\n\n return newSig !== oldSig;\n }\n\n /**\n * 获取最后一条消息的签名(用于变化检测)\n *\n * 使用消息的 role、content 和 toolCalls 生成签名\n */\n private getLastMessageSignature(messages: any[]): string {\n const lastMsg = messages[messages.length - 1];\n if (!lastMsg) return '';\n\n // 提取关键字段生成签名\n const sig = {\n r: lastMsg.role,\n c: lastMsg.content,\n // 工具调用只比较数量和名称(因为 toolCalls 可能包含动态 ID)\n tc: lastMsg.toolCalls?.map((tc: any) => ({ n: tc.name, a: tc.arguments }))\n };\n\n return JSON.stringify(sig);\n }\n\n /**\n * 处理注册工具\n */\n public handleRegisterTools(msg: any): void {\n const { agentId, tools } = msg;\n const session = this.agentSessions.get(agentId);\n if (session) {\n session.tools = [];\n for (const tool of tools) {\n const config = getToolRenderConfig(tool.name, tool.render);\n const callTemplate = config.call || 'json';\n const resultTemplate = config.result || 'json';\n\n // 检查是否为内联模板(对象类型)\n const callIsInline = typeof tool.render?.call === 'object' && tool.render.call !== null;\n const resultIsInline = typeof tool.render?.result === 'object' && tool.render.result !== null;\n\n session.tools.push({\n name: tool.name,\n description: tool.description,\n render: {\n call: callIsInline ? '__inline__' : callTemplate,\n result: resultIsInline ? '__inline__' : resultTemplate,\n inlineCall: callIsInline ? tool.render.call : undefined,\n inlineResult: resultIsInline ? tool.render.result : undefined,\n },\n });\n\n if (!TOOL_DISPLAY_NAMES[tool.name]) {\n (TOOL_DISPLAY_NAMES as Record<string, string>)[tool.name] = tool.name;\n }\n }\n console.log(`[Viewer Worker] Agent ${agentId} 已注册 ${tools.length} 个工具`);\n }\n }\n\n /**\n * 处理切换当前 Agent(IPC 消息)\n */\n public handleSetCurrentAgent(msg: any): void {\n const { agentId } = msg;\n if (this.agentSessions.has(agentId)) {\n this.currentAgentId = agentId;\n this.updateSessionActivity(agentId);\n console.log(`[Viewer Worker] 当前 Agent 已切换: ${agentId}`);\n }\n }\n\n /**\n * 处理注销 Agent\n */\n public handleUnregisterAgent(msg: any): void {\n const { agentId } = msg;\n this.agentSessions.delete(agentId);\n this.clearFeatureTemplates(agentId);\n console.log(`[Viewer Worker] Agent 已注销: ${agentId}`);\n\n // 如果注销的是当前 Agent,切换到另一个\n if (this.currentAgentId === agentId) {\n const remaining = Array.from(this.agentSessions.keys());\n this.currentAgentId = remaining.length > 0 ? remaining[0] : null;\n }\n }\n\n /**\n * 处理停止\n */\n public handleStop(): void {\n process.exit(0);\n }\n\n /**\n * 处理推送通知\n */\n public handlePushNotification(msg: any): void {\n const { agentId, notification } = msg;\n const session = this.agentSessions.get(agentId);\n if (!session) {\n return;\n }\n\n this.updateSessionActivity(agentId);\n\n if (notification?.type === 'log.entry' && notification?.data) {\n session.logs.push(this.normalizeLogEntry(notification.data, session));\n if (session.logs.length > this.MAX_LOGS) {\n session.logs.splice(0, session.logs.length - this.MAX_LOGS);\n }\n return;\n }\n\n const runtimeState = this.getSessionRuntimeState(session);\n\n // call 运行状态追踪(独立字段,不受 state 覆盖影响)\n if (notification.type === 'call.start') {\n session.callActive = true;\n const nextStage = runtimeState.activeToolCount > 0 ? 'tool_executing' : 'awaiting_runtime';\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, nextStage, notification.timestamp || Date.now()),\n callActive: true,\n callStartedAt: runtimeState.callActive === true\n ? (runtimeState.callStartedAt || notification.timestamp || Date.now())\n : (notification.timestamp || Date.now()),\n lastErrorType: null,\n lastErrorMessage: null,\n };\n } else if (notification.type === 'call.finish') {\n const finishData = (notification.data && typeof notification.data === 'object')\n ? notification.data as Record<string, unknown>\n : {};\n const completed = finishData.completed !== false;\n session.callActive = false;\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, completed ? 'completed' : 'failed', notification.timestamp || Date.now()),\n callActive: false,\n activeToolNames: [],\n activeToolCount: 0,\n retryAttempt: undefined,\n maxRetries: undefined,\n nextRetryDelayMs: undefined,\n };\n } else if (notification.type === 'llm.char_count') {\n const data = (notification.data && typeof notification.data === 'object')\n ? notification.data as Record<string, unknown>\n : {};\n const charCount = typeof data.charCount === 'number' ? data.charCount : runtimeState.charCount;\n const phase = typeof data.phase === 'string' ? data.phase : '';\n const toolCallCount = typeof data.toolCallCount === 'number' ? data.toolCallCount : runtimeState.toolCallCount;\n const nextStage = this.getRuntimeStageFromLLMPhase(phase);\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, nextStage, notification.timestamp || Date.now()),\n callActive: session.callActive === true,\n charCount,\n thinkingChars: typeof data.thinkingChars === 'number'\n ? data.thinkingChars\n : (phase === 'thinking' ? charCount : runtimeState.thinkingChars),\n contentChars: typeof data.contentChars === 'number'\n ? data.contentChars\n : (phase === 'content' ? charCount : runtimeState.contentChars),\n toolCallCount,\n };\n } else if (notification.type === 'llm.complete') {\n const nextStage = session.callActive === true\n ? (runtimeState.activeToolCount > 0 ? 'tool_executing' : 'awaiting_runtime')\n : 'completed';\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, nextStage, notification.timestamp || Date.now()),\n callActive: session.callActive === true,\n };\n } else if (notification.type === 'tool.start') {\n const data = (notification.data && typeof notification.data === 'object')\n ? notification.data as Record<string, unknown>\n : {};\n const toolName = typeof data.toolName === 'string' ? data.toolName.trim() : '';\n const activeToolNames = this.cloneRuntimeState(runtimeState).activeToolNames;\n if (toolName && !activeToolNames.includes(toolName)) {\n activeToolNames.push(toolName);\n }\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, 'tool_executing', notification.timestamp || Date.now()),\n callActive: session.callActive === true,\n activeToolNames,\n activeToolCount: activeToolNames.length,\n };\n } else if (notification.type === 'tool.complete') {\n const data = (notification.data && typeof notification.data === 'object')\n ? notification.data as Record<string, unknown>\n : {};\n const toolName = typeof data.toolName === 'string' ? data.toolName.trim() : '';\n const activeToolNames = this.cloneRuntimeState(runtimeState).activeToolNames\n .filter((name) => name !== toolName);\n const nextStage = session.callActive === true\n ? (activeToolNames.length > 0 ? 'tool_executing' : 'awaiting_runtime')\n : 'completed';\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, nextStage, notification.timestamp || Date.now()),\n callActive: session.callActive === true,\n activeToolNames,\n activeToolCount: activeToolNames.length,\n };\n }\n\n if (notification.category === 'state') {\n // 状态类通知:覆盖当前状态\n session.currentState = notification;\n } else if (notification.category === 'event') {\n // 事件类通知:追加到事件列表\n session.events.push(notification);\n session.lastEventCount++;\n }\n }\n\n private normalizeLogEntry(raw: any, session: AgentSession): DebugLogEntry {\n return {\n id: typeof raw?.id === 'string' ? raw.id : `log-${session.id}-${Date.now()}`,\n timestamp: typeof raw?.timestamp === 'number' ? raw.timestamp : Date.now(),\n level: raw?.level || 'info',\n message: typeof raw?.message === 'string' ? raw.message : String(raw?.message ?? ''),\n namespace: typeof raw?.namespace === 'string' ? raw.namespace : 'agent',\n context: {\n ...(raw?.context && typeof raw.context === 'object' ? raw.context : {}),\n agentId: raw?.context?.agentId || session.id,\n agentName: raw?.context?.agentName || session.name,\n },\n data: raw?.data,\n delivery: raw?.delivery && typeof raw.delivery === 'object'\n ? {\n hub: !!raw.delivery.hub,\n console: !!raw.delivery.console,\n reason: raw.delivery.reason || 'hub',\n }\n : {\n hub: true,\n console: false,\n reason: 'hub',\n },\n };\n }\n\n private withSessionLogContext(entry: DebugLogEntry, session: AgentSession): DebugLogEntry {\n return {\n ...entry,\n context: {\n ...entry.context,\n agentId: entry.context.agentId || session.id,\n agentName: entry.context.agentName || session.name,\n },\n };\n }\n\n private parseNumberParam(value: string | null): number | undefined {\n if (!value) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n\n private listAgentSummaries() {\n return Array.from(this.agentSessions.values())\n .map(session => createDebuggerAgentSummary(session, this.isSessionConnected(session)))\n .sort((a, b) => a.createdAt - b.createdAt);\n }\n\n private getAgentDetails(agentId: string) {\n const session = this.agentSessions.get(agentId);\n if (!session) return undefined;\n return createDebuggerAgentDetails(session, this.isSessionConnected(session));\n }\n\n private queryLogs(query: DebuggerLogQuery) {\n const scope: 'current' | 'all' = query.scope === 'all' ? 'all' : 'current';\n const selectedAgentId = query.agentId || this.currentAgentId;\n const requestedOffset = typeof query.offset === 'number' ? query.offset : 0;\n const hasExplicitLimit = typeof query.limit === 'number';\n const isUnboundedQuery = !hasExplicitLimit\n && requestedOffset === 0\n && !query.agentId\n && !query.level\n && !query.namespace\n && !query.feature\n && !query.lifecycle\n && typeof query.from !== 'number'\n && typeof query.to !== 'number'\n && !query.search;\n\n let logs: DebugLogEntry[] = [];\n if (scope === 'all') {\n for (const session of this.agentSessions.values()) {\n for (const entry of session.logs) {\n logs.push(this.withSessionLogContext(entry, session));\n }\n }\n } else {\n const effectiveAgentId = selectedAgentId || this.currentAgentId;\n const session = effectiveAgentId ? this.agentSessions.get(effectiveAgentId) : undefined;\n logs = session ? session.logs.map((entry) => this.withSessionLogContext(entry, session)) : [];\n }\n\n logs.sort((a, b) => a.timestamp - b.timestamp);\n const filtered = filterDebuggerLogs(logs, {\n agentId: query.agentId,\n level: query.level,\n namespace: query.namespace,\n feature: query.feature,\n lifecycle: query.lifecycle,\n from: query.from,\n to: query.to,\n search: query.search,\n });\n const total = filtered.length;\n const effectiveLimit = hasExplicitLimit\n ? query.limit\n : isUnboundedQuery\n ? QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT\n : undefined;\n const paged = filterDebuggerLogs(logs, {\n agentId: query.agentId,\n level: query.level,\n namespace: query.namespace,\n feature: query.feature,\n lifecycle: query.lifecycle,\n from: query.from,\n to: query.to,\n limit: effectiveLimit,\n offset: query.offset,\n search: query.search,\n });\n const visibleAfterOffset = Math.max(0, total - requestedOffset);\n const truncated = typeof effectiveLimit === 'number' && paged.length < visibleAfterOffset;\n\n return {\n scope,\n currentAgentId: this.currentAgentId,\n selectedAgentId,\n total,\n logs: paged,\n truncation: truncated\n ? {\n truncated: true,\n appliedLimit: effectiveLimit,\n returnedCount: paged.length,\n availableCount: visibleAfterOffset,\n nextOffset: requestedOffset + paged.length,\n reason: isUnboundedQuery\n ? 'query_logs was called without narrowing parameters, so the server applied a safety cap.'\n : 'The requested result window was smaller than the available matching logs.',\n guidance: `Add narrowing parameters such as level, namespace, feature, lifecycle, from/to, search, or pass limit/offset explicitly. For example: {\"limit\": ${QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT}, \"offset\": ${requestedOffset + paged.length}}`,\n }\n : {\n truncated: false,\n returnedCount: paged.length,\n availableCount: visibleAfterOffset,\n },\n collectionPolicy: {\n hubConnected: this.udsClients.size > 0,\n includesOnlyHubDeliveredLogs: true,\n fallbackBehavior: 'Logs emitted without an active debugger connection fall back to local console output and do not appear here.',\n },\n };\n }\n\n private async handleMCP(req: IncomingMessage, res: ServerResponse): Promise<void> {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Headers', 'content-type, mcp-session-id, last-event-id, x-agentdev-agent-id');\n res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method !== 'POST') {\n res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Method not allowed.',\n },\n id: null,\n }));\n return;\n }\n\n try {\n await this.debuggerMcp.handleRequest(req, res);\n } catch (error) {\n console.error('[Viewer Worker] MCP request failed:', error);\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: 'Internal server error',\n },\n id: null,\n }));\n }\n }\n }\n\n /**\n * 处理用户输入请求\n */\n public handleRequestInput(msg: any): void {\n const { agentId, requestId, prompt } = msg;\n console.log(`[Viewer Worker] 收到输入请求: agentId=${agentId}, requestId=${requestId}`);\n\n const session = this.agentSessions.get(agentId);\n if (!session) {\n console.warn(`[Viewer Worker] Unknown agent for input request: ${agentId}`);\n return;\n }\n\n this.updateSessionActivity(agentId);\n\n // 初始化 pendingInputRequests(如不存在)\n let pendingRequests = (session as any).pendingInputRequests as Map<string, any>;\n if (!pendingRequests) {\n pendingRequests = new Map();\n (session as any).pendingInputRequests = pendingRequests;\n }\n\n // 存储请求\n pendingRequests.set(requestId, {\n prompt,\n placeholder: msg.placeholder,\n initialValue: (msg as any).initialValue,\n actions: msg.actions,\n mode: msg.mode,\n questions: msg.questions,\n timestamp: Date.now(),\n });\n\n console.log(`[Viewer Worker] Input request 已存储: ${requestId}, 当前队列大小: ${pendingRequests.size}`);\n }\n\n /**\n * 处理静态工具渲染文件\n * 直接返回已编译的 .js 文件内容\n * 路径规则:/tools/{category}/{filename}.js → dist/tools/{category}/{filename}.render.js\n * \n * 支持从多个位置查找:\n * 1. 项目根目录 dist/tools/\n * 2. npm 包 node_modules/agentdev/dist/tools/\n */\n public handleStaticToolFile(req: IncomingMessage, res: ServerResponse, url: string): void {\n try {\n // 解析路径: /tools/system/shell.render.js\n const relativePath = url.substring('/tools/'.length);\n\n // 获取当前 Agent 的项目根目录\n const currentSession = this.currentAgentId ? this.agentSessions.get(this.currentAgentId) : undefined;\n const projectRoot = currentSession?.projectRoot || process.cwd();\n\n // 计算可能的文件路径\n const searchPaths = [\n // 1. 项目根目录 dist/tools/\n join(projectRoot, 'dist/tools', relativePath),\n // 2. npm 包 node_modules/agentdev/dist/tools/\n join(projectRoot, 'node_modules/agentdev/dist/tools', relativePath),\n ];\n\n // 按顺序尝试每个路径\n this.tryReadFile(searchPaths, 0, res, url);\n } catch (err: any) {\n console.error('[Viewer Worker] 静态文件处理错误:', err.message);\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal server error');\n }\n }\n\n /**\n * 尝试从多个路径读取文件\n */\n private tryReadFile(paths: string[], index: number, res: ServerResponse, originalUrl: string): void {\n if (index >= paths.length) {\n console.error(`[Viewer Worker] 模板未找到,尝试了所有路径: ${paths.join(', ')}`);\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end(`Template not found: ${originalUrl}`);\n return;\n }\n\n const currentPath = paths[index];\n \n import('fs').then((fs) => {\n fs.readFile(currentPath, 'utf-8', (err: Error | null, data: string) => {\n if (err) {\n // 当前路径失败,尝试下一个\n this.tryReadFile(paths, index + 1, res, originalUrl);\n return;\n }\n\n // 成功读取\n res.writeHead(200, {\n 'Content-Type': 'application/javascript; charset=utf-8',\n 'Access-Control-Allow-Origin': '*',\n });\n res.end(data);\n });\n }).catch((err) => {\n console.error('[Viewer Worker] fs 模块加载失败:', err.message);\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal server error');\n });\n }\n\n /**\n * 处理统一的 Feature 模板路由(新格式)\n * URL 格式: /template/{packageName}/{templateName}.render.js\n * \n * 统一支持三种来源:\n * 1. 框架内置 Feature:/template/agentdev/visual/capture.render.js\n * 映射到: node_modules/agentdev/dist/features/visual/templates/capture.render.js\n * 或: dist/features/visual/templates/capture.render.js(开发模式)\n * \n * 2. 外部 npm 包:/template/@agentdev/shell-feature/bash.render.js\n * 映射到: node_modules/@agentdev/shell-feature/dist/templates/bash.render.js\n * \n * 3. 用户本地 Feature:/template/my-project/visual/capture.render.js\n * 映射到: dist/templates/capture.render.js\n * 或: dist/features/visual/templates/capture.render.js\n */\n public handleUnifiedTemplate(req: IncomingMessage, res: ServerResponse, url: string): void {\n try {\n // 解析 URL: /template/{packageName}/{templateName}.render.js\n // 支持普通包名和 scope 包名(@scope/name)\n // 例如:\n // - /template/agentdev/visual/capture.render.js -> packageName=agentdev, templateFile=visual/capture.render.js\n // - /template/@agentdev/visual-feature/capture.render.js -> packageName=@agentdev/visual-feature, templateFile=capture.render.js\n const match = url.match(/^\\/template\\/((?:@[^/]+\\/)?[^/]+)\\/(.+\\.render\\.js)$/);\n if (!match) {\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Invalid template path format');\n return;\n }\n\n const [, packageName, templateFile] = match;\n const templateFileTs = templateFile.replace('.render.js', '.render.ts');\n\n // 获取当前 Agent 的项目根目录\n const currentSession = this.currentAgentId ? this.agentSessions.get(this.currentAgentId) : undefined;\n const projectRoot = currentSession?.projectRoot || process.cwd();\n\n // 构建可能的文件路径(按优先级)\n const searchPaths: string[] = [];\n\n // 1. 外部 npm 包(包括 scope 包)\n if (packageName.startsWith('@')) {\n // scoped package: @scope/name\n searchPaths.push(\n join(projectRoot, 'node_modules', packageName, 'dist', 'templates', templateFile),\n join(projectRoot, 'node_modules', packageName, 'dist', 'templates', templateFileTs),\n );\n } else if (packageName === 'agentdev') {\n // 2. 框架内置 Feature(agentdev 包)\n // 需要从模板名推断 feature 名称\n // 例如: visual/capture.render.js -> feature=visual, template=capture.render.js\n const templateParts = templateFile.split('/');\n if (templateParts.length === 2) {\n const featureName = templateParts[0];\n const templateName = templateParts[1];\n \n searchPaths.push(\n // 开发模式:项目根目录 dist/features/\n join(projectRoot, 'dist', 'features', featureName, 'templates', templateName),\n join(projectRoot, 'dist', 'features', featureName, 'templates', templateName.replace('.js', '.ts')),\n // 源码模式:项目根目录 src/features/\n join(projectRoot, 'src', 'features', featureName, 'templates', templateName.replace('.js', '.ts')),\n // npm 包模式:node_modules/agentdev/dist/features/\n join(projectRoot, 'node_modules', 'agentdev', 'dist', 'features', featureName, 'templates', templateName),\n );\n } else {\n // 兜底:直接在 dist/templates 查找\n searchPaths.push(\n join(projectRoot, 'dist', 'templates', templateFile),\n join(projectRoot, 'dist', 'templates', templateFileTs),\n );\n }\n } else {\n // 3. 用户本地 Feature(其他包名)\n // 尝试在项目的 dist/templates 或 dist/features/*/templates 查找\n searchPaths.push(\n join(projectRoot, 'dist', 'templates', templateFile),\n join(projectRoot, 'dist', 'templates', templateFileTs),\n // 也尝试 feature 子目录\n join(projectRoot, 'dist', 'features', templateFile),\n join(projectRoot, 'dist', 'features', templateFileTs),\n // 源码目录\n join(projectRoot, 'src', 'templates', templateFileTs),\n join(projectRoot, 'src', 'features', templateFileTs),\n );\n }\n\n // 按顺序尝试每个路径\n this.tryReadFile(searchPaths, 0, res, url);\n } catch (err: any) {\n console.error('[Viewer Worker] Unified template handler error:', err.message);\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal server error');\n }\n }\n\n /**\n * 处理 Feature 渲染模板文件\n * 解析路径: /features/shell/trash-delete.render.js\n * \n * 支持从多个位置查找:\n * 1. 项目根目录 dist/features/{feature}/templates/\n * 2. 项目根目录 src/features/{feature}/templates/\n * 3. npm 包 node_modules/agentdev/dist/features/{feature}/templates/\n * \n * 支持扩展名映射:\n * - .js → 同时尝试 .js 和 .ts\n */\n public handleFeatureTemplate(req: IncomingMessage, res: ServerResponse, url: string): void {\n try {\n // 解析路径: /features/shell/trash-delete.render.js\n const match = url.match(/^\\/features\\/([^/]+)\\/(.+\\.render\\.js)$/);\n if (!match) {\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Invalid feature template path');\n return;\n }\n\n const [, featureName, templateFile] = match;\n // 同时尝试 .js 和 .ts 扩展名\n const templateFileTs = templateFile.replace('.render.js', '.render.ts');\n\n // 获取当前 Agent 的项目根目录\n const currentSession = this.currentAgentId ? this.agentSessions.get(this.currentAgentId) : undefined;\n const projectRoot = currentSession?.projectRoot || process.cwd();\n\n // 构建可能的文件路径(按优先级)\n const searchPaths = [\n // 1. 项目根目录 dist/features/ (.js 编译后)\n join(projectRoot, 'dist', 'features', featureName, 'templates', templateFile),\n // 2. 项目根目录 dist/features/ (.ts 源码)\n join(projectRoot, 'dist', 'features', featureName, 'templates', templateFileTs),\n // 3. 项目根目录 src/features/ (.ts 源码)\n join(projectRoot, 'src', 'features', featureName, 'templates', templateFileTs),\n // 4. 项目根目录 src/features/ (.js 如果存在)\n join(projectRoot, 'src', 'features', featureName, 'templates', templateFile),\n // 5. npm 包 node_modules/agentdev/dist/features/\n join(projectRoot, 'node_modules', 'agentdev', 'dist', 'features', featureName, 'templates', templateFile),\n ];\n\n // 按顺序尝试每个路径\n this.tryReadFile(searchPaths, 0, res, url);\n } catch (err: any) {\n console.error('[Viewer Worker] Feature 模板处理错误:', err.message);\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal server error');\n }\n }\n\n /**\n * 处理 npm 包中的 Feature 模板\n * 支持两种路径格式:\n * 1. 独立包: /npm/@agentdev/shell-feature/templates/bash.render.js\n * 映射到: node_modules/@agentdev/shell-feature/dist/templates/bash.render.js\n * 2. 框架包: /npm/agentdev/features/shell/bash.render.js\n * 映射到: node_modules/agentdev/dist/features/shell/templates/bash.render.js\n */\n public handleNpmFeatureTemplate(req: IncomingMessage, res: ServerResponse, url: string): void {\n try {\n // 获取当前 Agent 的项目根目录\n const currentSession = this.currentAgentId ? this.agentSessions.get(this.currentAgentId) : undefined;\n const projectRoot = currentSession?.projectRoot || process.cwd();\n\n let templatePath: string;\n\n // 模式 1: 独立 npm 包 /npm/@scope/package/templates/xxx.render.js\n const scopedMatch = url.match(/^\\/npm\\/(@[^/]+\\/[^/]+)\\/templates\\/(.+\\.render\\.js)$/);\n if (scopedMatch) {\n const [, scopedPackageName, templateFile] = scopedMatch;\n // 构建路径: node_modules/@scope/package/dist/templates/{template}\n templatePath = join(projectRoot, 'node_modules', scopedPackageName, 'dist', 'templates', templateFile);\n } else {\n // 模式 2: 独立 npm 包(无 scope)/npm/package/templates/xxx.render.js\n const simpleMatch = url.match(/^\\/npm\\/([^/@][^/]*)\\/templates\\/(.+\\.render\\.js)$/);\n if (simpleMatch) {\n const [, packageName, templateFile] = simpleMatch;\n templatePath = join(projectRoot, 'node_modules', packageName, 'dist', 'templates', templateFile);\n } else {\n // 模式 3: 框架包 /npm/agentdev/features/shell/bash.render.js\n const frameworkMatch = url.match(/^\\/npm\\/([^/]+)\\/features\\/([^/]+)\\/(.+\\.render\\.js)$/);\n if (!frameworkMatch) {\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Invalid npm feature template path');\n return;\n }\n const [, packageName, featureName, templateFile] = frameworkMatch;\n // 构建路径: node_modules/{package}/dist/features/{feature}/templates/{template}\n templatePath = join(projectRoot, 'node_modules', packageName, 'dist', 'features', featureName, 'templates', templateFile);\n }\n }\n\n // 读取文件并返回\n import('fs').then((fs) => {\n fs.readFile(templatePath, 'utf-8', (err: any, data: string) => {\n if (err) {\n console.error('[Viewer Worker] 读取 npm Feature 模板失败:', {\n path: templatePath,\n error: err.message\n });\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end(`npm feature template not found: ${url}`);\n return;\n }\n\n res.writeHead(200, {\n 'Content-Type': 'application/javascript; charset=utf-8',\n 'Access-Control-Allow-Origin': '*',\n });\n res.end(data);\n });\n }).catch((err) => {\n console.error('[Viewer Worker] fs 模块加载失败:', err.message);\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal server error');\n });\n } catch (err: any) {\n console.error('[Viewer Worker] npm Feature 模板处理错误:', err.message);\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal server error');\n }\n }\n\n /**\n * 处理静态资源文件(chunk、js、css 等)\n * 支持 agentdev npm 包中的共享模块\n */\n public handleStaticAsset(req: IncomingMessage, res: ServerResponse, url: string): void {\n try {\n // 获取当前 Agent 的项目根目录\n const session = this.currentAgentId ? this.agentSessions.get(this.currentAgentId) : undefined;\n const projectRoot = session?.projectRoot || process.cwd();\n\n // 提取文件名(去掉开头的 /)\n const fileName = url.substring(1); // 如 /chunk-xxx.js -> chunk-xxx.js\n\n // 构建搜索路径\n const searchPaths = [\n // npm 包模式:node_modules/agentdev/dist/{file}\n join(projectRoot, 'node_modules', 'agentdev', 'dist', fileName),\n // 开发模式:项目根目录 dist/{file}\n join(projectRoot, 'dist', fileName),\n ];\n\n // 按顺序尝试每个路径\n this.tryReadFile(searchPaths, 0, res, url);\n } catch (err: any) {\n console.error('[Viewer Worker] 静态资源处理错误:', err.message);\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal server error');\n }\n }\n\n // ========== HTML 生成(复用原有代码)==========\n\n private getHtml(): string {\n return generateViewerHtml(this.port);\n }\n}\n\n// 导出 ViewerWorker 类供外部使用\nexport { ViewerWorker };\n\n// ========== Worker 进程入口(仅当直接运行时执行)==========\n\n// 检查是否为主模块(不是被其他模块导入)\n const isMainModule = (url: string): boolean => {\n const mainArg = process.argv[1];\n if (!mainArg) return false;\n const mainPath = mainArg.replace(/\\\\/g, '/');\n const modulePath = url.startsWith('file://') ? url.substring(7) : url;\n return modulePath.endsWith(mainPath) || mainPath.endsWith(modulePath);\n};\n\nif (isMainModule(import.meta.url)) {\n // 全局错误处理\n process.on('uncaughtException', (err) => {\n console.error('[Viewer Worker] 未捕获的异常:', err);\n console.error(err.stack);\n });\n\n process.on('unhandledRejection', (reason) => {\n console.error('[Viewer Worker] 未处理的 Promise 拒绝:', reason);\n });\n\n const port = parseInt(process.argv[2] || process.env.AGENTDEV_PORT || '2026', 10);\n const openBrowser = process.argv[3] !== 'false' && process.env.AGENTDEV_OPEN_BROWSER !== 'false';\n const udsPath = process.env.AGENTDEV_UDS_PATH || process.argv[4];\n const worker = new ViewerWorker(port, openBrowser, udsPath);\n\n worker.start().catch(err => {\n console.error('[Viewer Worker] 启动失败:', err);\n process.exit(1);\n });\n\n // 监听主进程消息\n process.on('message', (msg: DebugHubIPCMessage) => {\n switch (msg.type) {\n case 'register-agent':\n worker.handleRegisterAgent(msg);\n break;\n case 'update-agent-inspector':\n worker.handleUpdateAgentInspector(msg);\n break;\n case 'update-agent-overview':\n worker.handleUpdateAgentOverview(msg);\n break;\n case 'push-messages':\n worker.handlePushMessages(msg);\n break;\n case 'register-tools':\n worker.handleRegisterTools(msg);\n break;\n case 'set-current-agent':\n worker.handleSetCurrentAgent(msg);\n break;\n case 'unregister-agent':\n worker.handleUnregisterAgent(msg);\n break;\n case 'push-notification':\n worker.handlePushNotification(msg);\n break;\n case 'request-input':\n worker.handleRequestInput(msg);\n break;\n case 'stop':\n worker.handleStop();\n break;\n }\n });\n\n // 优雅退出\n process.on('SIGINT', () => {\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n process.exit(0);\n });\n}\n","import type { IncomingMessage, ServerResponse } from 'http';\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport * as z from 'zod/v4';\nimport type { AgentLogsResponse, AgentSession, DebugLogEntry, HookInspectorSnapshot } from './types.js';\n\nconst QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT = 200;\n\nexport interface DebuggerAgentSummary {\n id: string;\n name: string;\n createdAt: number;\n lastActive: number;\n connected: boolean;\n messageCount: number;\n toolCount: number;\n logCount: number;\n hasHookInspector: boolean;\n}\n\nexport interface DebuggerAgentDetails extends DebuggerAgentSummary {\n projectRoot?: string;\n currentStateType?: string | null;\n pendingInputCount: number;\n hookInspector?: HookInspectorSnapshot;\n}\n\nexport interface DebuggerLogQuery {\n scope?: 'current' | 'all';\n agentId?: string | null;\n currentAgentId?: string | null;\n selectedAgentId?: string | null;\n level?: string;\n namespace?: string;\n feature?: string;\n lifecycle?: string;\n from?: number;\n to?: number;\n limit?: number;\n offset?: number;\n search?: string;\n}\n\nexport interface DebuggerMCPDataSource {\n listAgents(): DebuggerAgentSummary[];\n getAgent(agentId: string): DebuggerAgentDetails | undefined;\n getCurrentAgentId(): string | null;\n getHooks(agentId: string): HookInspectorSnapshot | undefined;\n queryLogs(query: DebuggerLogQuery): AgentLogsResponse;\n}\n\nexport const DEBUGGER_MCP_TOOL_DEFINITIONS = [\n {\n name: 'list_agents',\n description: 'List all debugger-visible agents and basic session status.',\n },\n {\n name: 'get_current_agent',\n description: 'Get the currently selected agent in the debugger.',\n },\n {\n name: 'get_agent',\n description: 'Get a single agent by id. Supports \"current\" and \"self\".',\n },\n {\n name: 'get_hooks',\n description: 'Get the hook inspector snapshot for an agent.',\n },\n {\n name: 'query_logs',\n description: 'Query structured debugger-hub logs. Always prefer adding filters such as level, namespace, feature, lifecycle, time range, search, limit, and offset. If you call this without narrowing parameters, the server truncates the response to a large safety cap and returns truncation guidance so you can continue with refined parameters.',\n },\n] as const;\n\nexport const DEBUGGER_MCP_RESOURCE_DEFINITIONS = [\n {\n uri: 'debug://agents',\n description: 'All visible debugger agents.',\n },\n {\n uri: 'debug://agents/current',\n description: 'Currently selected agent in the debugger.',\n },\n {\n uri: 'debug://agents/{agentId}',\n description: 'Detailed agent session snapshot for a specific agent.',\n },\n {\n uri: 'debug://agents/{agentId}/hooks',\n description: 'Hook inspector snapshot for a specific agent.',\n },\n] as const;\n\nexport const DEBUGGER_MCP_PROMPT_DEFINITIONS = [\n {\n name: 'analyze_errors',\n description: 'Summarize recent error logs for an agent and suggest likely causes.',\n },\n {\n name: 'review_hooks',\n description: 'Review an agent hook snapshot and identify ordering or binding issues.',\n },\n {\n name: 'diagnose_agent',\n description: 'Produce a high-level diagnosis from the current agent snapshot, hooks, and recent warnings/errors.',\n },\n] as const;\n\nfunction jsonText(value: unknown): string {\n return JSON.stringify(value, null, 2);\n}\n\nfunction createTextResult<T extends Record<string, unknown>>(text: string, structuredContent?: T) {\n return structuredContent\n ? {\n content: [{ type: 'text' as const, text }],\n structuredContent,\n }\n : {\n content: [{ type: 'text' as const, text }],\n };\n}\n\nfunction normalizeOptionalString(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction normalizeOptionalNumber(value: number | undefined): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nexport class DebuggerMCPServer {\n constructor(private readonly dataSource: DebuggerMCPDataSource) {}\n\n async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const server = this.createServer();\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n });\n\n let closed = false;\n const close = async () => {\n if (closed) return;\n closed = true;\n await transport.close().catch(() => undefined);\n await server.close().catch(() => undefined);\n };\n\n res.on('close', () => {\n void close();\n });\n\n try {\n await server.connect(transport);\n await transport.handleRequest(req, res);\n } finally {\n if (!res.writableEnded) {\n await close();\n }\n }\n }\n\n private createServer(): McpServer {\n const server = new McpServer(\n {\n name: 'agentdev-debugger',\n version: '0.1.0',\n },\n {\n capabilities: {\n logging: {},\n },\n }\n );\n\n this.registerTools(server);\n this.registerResources(server);\n this.registerPrompts(server);\n return server;\n }\n\n private registerTools(server: McpServer): void {\n server.registerTool('list_agents', {\n title: 'List Agents',\n description: 'List all debugger-visible agents and basic session status.',\n inputSchema: z.object({}).optional(),\n }, async () => {\n const agents = this.dataSource.listAgents();\n return createTextResult(jsonText({ agents }), { agents });\n });\n\n server.registerTool('get_current_agent', {\n title: 'Get Current Agent',\n description: 'Get the currently selected agent in the debugger.',\n inputSchema: z.object({}).optional(),\n }, async () => {\n const currentAgentId = this.dataSource.getCurrentAgentId();\n const agent = currentAgentId ? this.dataSource.getAgent(currentAgentId) : undefined;\n return createTextResult(jsonText({ currentAgentId, agent: agent || null }), {\n currentAgentId,\n agent: agent || null,\n });\n });\n\n server.registerTool('get_agent', {\n title: 'Get Agent',\n description: 'Get a single agent by id. Supports \"current\" and \"self\".',\n inputSchema: z.object({\n agentId: z.string().optional().describe('Agent ID, \"current\", or \"self\". Defaults to current.'),\n callerAgentId: z.string().optional().describe('Optional caller agent id used to resolve \"self\".'),\n }),\n }, async ({ agentId, callerAgentId }, extra) => {\n const resolvedAgentId = this.resolveAgentRef(agentId, callerAgentId, extra);\n const agent = resolvedAgentId ? this.dataSource.getAgent(resolvedAgentId) : undefined;\n return createTextResult(jsonText({\n requestedAgentId: agentId || 'current',\n resolvedAgentId,\n agent: agent || null,\n }), {\n requestedAgentId: agentId || 'current',\n resolvedAgentId,\n agent: agent || null,\n });\n });\n\n server.registerTool('get_hooks', {\n title: 'Get Hooks',\n description: 'Get the hook inspector snapshot for an agent.',\n inputSchema: z.object({\n agentId: z.string().optional().describe('Agent ID, \"current\", or \"self\". Defaults to current.'),\n callerAgentId: z.string().optional().describe('Optional caller agent id used to resolve \"self\".'),\n }),\n }, async ({ agentId, callerAgentId }, extra) => {\n const resolvedAgentId = this.resolveAgentRef(agentId, callerAgentId, extra);\n const hooks = resolvedAgentId ? this.dataSource.getHooks(resolvedAgentId) : undefined;\n return createTextResult(jsonText({\n requestedAgentId: agentId || 'current',\n resolvedAgentId,\n hooks: hooks || { lifecycleOrder: [], features: [], hooks: [] },\n }), {\n requestedAgentId: agentId || 'current',\n resolvedAgentId,\n hooks: hooks || { lifecycleOrder: [], features: [], hooks: [] },\n });\n });\n\n server.registerTool('query_logs', {\n title: 'Query Logs',\n description: 'Query structured logs that were successfully delivered to the debugger hub. Use filters such as level, namespace, feature, lifecycle, from/to, search, limit, and offset to keep the result focused. If you omit narrowing parameters, the server automatically truncates the response to a large cap and returns explicit truncation guidance so you can continue with refined parameters. Logs emitted while the debugger was disconnected fall back to local console output and do not appear here.',\n inputSchema: z.object({\n agentId: z.string().optional().describe('Agent ID, \"current\", \"self\", or omitted. Use this to narrow the result to one agent.'),\n callerAgentId: z.string().optional().describe('Optional caller agent id used to resolve \"self\".'),\n scope: z.enum(['current', 'all']).optional().describe('Query the current agent or all visible agents. \"all\" can be large, so pair it with filters or limit.'),\n level: z.string().optional().describe('Exact log level filter, for example \"error\" or \"warn\".'),\n namespace: z.string().optional().describe('Substring match on the log namespace.'),\n feature: z.string().optional().describe('Exact feature name filter.'),\n lifecycle: z.string().optional().describe('Exact lifecycle / hook stage filter.'),\n from: z.number().int().optional().describe('Inclusive start timestamp in ms.'),\n to: z.number().int().optional().describe('Inclusive end timestamp in ms.'),\n limit: z.number().int().positive().max(500).optional().describe(`Maximum number of logs to return. When omitted on an otherwise unbounded query, the server applies a default cap of ${QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT} and marks the response as truncated.`),\n offset: z.number().int().min(0).optional().describe('Pagination offset. Use together with limit to continue from a truncated or paged result.'),\n search: z.string().optional().describe('Substring search over the log message and serialized JSON payload/context.'),\n }),\n }, async (args, extra) => {\n const resolvedAgentId = this.resolveAgentRef(args.agentId, args.callerAgentId, extra);\n const scope = args.scope === 'all' ? 'all' : 'current';\n const queryAgentId = args.agentId && args.agentId !== 'current'\n ? resolvedAgentId || undefined\n : undefined;\n const result = this.dataSource.queryLogs({\n scope,\n agentId: queryAgentId,\n level: normalizeOptionalString(args.level),\n namespace: normalizeOptionalString(args.namespace),\n feature: normalizeOptionalString(args.feature),\n lifecycle: normalizeOptionalString(args.lifecycle),\n from: normalizeOptionalNumber(args.from),\n to: normalizeOptionalNumber(args.to),\n limit: normalizeOptionalNumber(args.limit),\n offset: normalizeOptionalNumber(args.offset),\n search: normalizeOptionalString(args.search),\n });\n const payload = {\n ...result,\n requestedAgentId: args.agentId || 'current',\n resolvedAgentId,\n };\n const text = result.truncation?.truncated\n ? [\n `query_logs response truncated after ${result.truncation.returnedCount} entries (available after offset: ${result.truncation.availableCount}).`,\n result.truncation.guidance || `Retry with explicit filters or pass limit/offset, for example {\"limit\": ${QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT}, \"offset\": ${result.truncation.nextOffset || result.truncation.returnedCount}}.`,\n '',\n jsonText(payload),\n ].join('\\n')\n : jsonText(payload);\n\n return createTextResult(text, payload);\n });\n }\n\n private registerResources(server: McpServer): void {\n server.registerResource('agents', 'debug://agents', {\n title: 'Debugger Agents',\n description: 'All visible debugger agents.',\n mimeType: 'application/json',\n }, async () => {\n const agents = this.dataSource.listAgents();\n return {\n contents: [{\n uri: 'debug://agents',\n mimeType: 'application/json',\n text: jsonText({ agents }),\n }],\n };\n });\n\n server.registerResource('current-agent', 'debug://agents/current', {\n title: 'Current Agent',\n description: 'Currently selected agent in the debugger.',\n mimeType: 'application/json',\n }, async () => {\n const currentAgentId = this.dataSource.getCurrentAgentId();\n const agent = currentAgentId ? this.dataSource.getAgent(currentAgentId) : undefined;\n return {\n contents: [{\n uri: 'debug://agents/current',\n mimeType: 'application/json',\n text: jsonText({ currentAgentId, agent: agent || null }),\n }],\n };\n });\n\n server.registerResource(\n 'agent-details',\n new ResourceTemplate('debug://agents/{agentId}', { list: undefined }),\n {\n title: 'Agent Details',\n description: 'Detailed agent session snapshot for a specific agent.',\n mimeType: 'application/json',\n },\n async (uri, variables) => {\n const agentId = this.stringVar(variables.agentId);\n const agent = agentId ? this.dataSource.getAgent(agentId) : undefined;\n return {\n contents: [{\n uri: uri.toString(),\n mimeType: 'application/json',\n text: jsonText({ agentId, agent: agent || null }),\n }],\n };\n }\n );\n\n server.registerResource(\n 'agent-hooks',\n new ResourceTemplate('debug://agents/{agentId}/hooks', { list: undefined }),\n {\n title: 'Agent Hooks',\n description: 'Hook inspector snapshot for a specific agent.',\n mimeType: 'application/json',\n },\n async (uri, variables) => {\n const agentId = this.stringVar(variables.agentId);\n const hooks = agentId ? this.dataSource.getHooks(agentId) : undefined;\n return {\n contents: [{\n uri: uri.toString(),\n mimeType: 'application/json',\n text: jsonText({\n agentId,\n hooks: hooks || { lifecycleOrder: [], features: [], hooks: [] },\n }),\n }],\n };\n }\n );\n }\n\n private registerPrompts(server: McpServer): void {\n server.registerPrompt('analyze_errors', {\n title: 'Analyze Errors',\n description: 'Summarize recent error logs for an agent and suggest likely causes.',\n argsSchema: {\n agentId: z.string().optional().describe('Agent ID, \"current\", or \"self\". Defaults to current.'),\n },\n }, async ({ agentId }) => {\n const resolvedAgentId = this.resolvePromptAgent(agentId);\n const agent = resolvedAgentId ? this.dataSource.getAgent(resolvedAgentId) : undefined;\n const logs = this.dataSource.queryLogs({\n scope: 'current',\n agentId: resolvedAgentId,\n level: 'error',\n limit: 20,\n });\n\n return {\n messages: [{\n role: 'user',\n content: {\n type: 'text',\n text: [\n 'Analyze the recent debugger errors for this agent.',\n 'Focus on root cause, repeated failure patterns, and concrete next checks.',\n '',\n `Requested agent: ${agentId || 'current'}`,\n `Resolved agent: ${resolvedAgentId || 'none'}`,\n '',\n 'Agent snapshot:',\n jsonText(agent || null),\n '',\n 'Recent error logs:',\n jsonText(logs.logs),\n ].join('\\n'),\n },\n }],\n };\n });\n\n server.registerPrompt('review_hooks', {\n title: 'Review Hooks',\n description: 'Review an agent hook snapshot and identify ordering or binding issues.',\n argsSchema: {\n agentId: z.string().optional().describe('Agent ID, \"current\", or \"self\". Defaults to current.'),\n },\n }, async ({ agentId }) => {\n const resolvedAgentId = this.resolvePromptAgent(agentId);\n const hooks = resolvedAgentId ? this.dataSource.getHooks(resolvedAgentId) : undefined;\n const agent = resolvedAgentId ? this.dataSource.getAgent(resolvedAgentId) : undefined;\n\n return {\n messages: [{\n role: 'user',\n content: {\n type: 'text',\n text: [\n 'Review this debugger hook snapshot.',\n 'Look for missing hooks, ordering surprises, disabled features, and likely wiring mistakes.',\n '',\n `Requested agent: ${agentId || 'current'}`,\n `Resolved agent: ${resolvedAgentId || 'none'}`,\n '',\n 'Agent snapshot:',\n jsonText(agent || null),\n '',\n 'Hook snapshot:',\n jsonText(hooks || { lifecycleOrder: [], features: [], hooks: [] }),\n ].join('\\n'),\n },\n }],\n };\n });\n\n server.registerPrompt('diagnose_agent', {\n title: 'Diagnose Agent',\n description: 'Produce a high-level diagnosis from the current agent snapshot, hooks, and recent warnings/errors.',\n argsSchema: {\n agentId: z.string().optional().describe('Agent ID, \"current\", or \"self\". Defaults to current.'),\n },\n }, async ({ agentId }) => {\n const resolvedAgentId = this.resolvePromptAgent(agentId);\n const agent = resolvedAgentId ? this.dataSource.getAgent(resolvedAgentId) : undefined;\n const hooks = resolvedAgentId ? this.dataSource.getHooks(resolvedAgentId) : undefined;\n const logs = this.dataSource.queryLogs({\n scope: 'current',\n agentId: resolvedAgentId,\n limit: 50,\n });\n\n return {\n messages: [{\n role: 'user',\n content: {\n type: 'text',\n text: [\n 'Diagnose this agent using the debugger snapshot.',\n 'Summarize health, likely bottlenecks, suspicious hook/tool wiring, and the next debugging steps.',\n '',\n `Requested agent: ${agentId || 'current'}`,\n `Resolved agent: ${resolvedAgentId || 'none'}`,\n '',\n 'Agent snapshot:',\n jsonText(agent || null),\n '',\n 'Hook snapshot:',\n jsonText(hooks || { lifecycleOrder: [], features: [], hooks: [] }),\n '',\n 'Recent logs:',\n jsonText(logs.logs),\n ].join('\\n'),\n },\n }],\n };\n });\n }\n\n private resolvePromptAgent(agentId: string | undefined): string | null {\n if (!agentId || agentId === 'current' || agentId === 'self') {\n return this.dataSource.getCurrentAgentId();\n }\n return agentId;\n }\n\n private resolveAgentRef(\n requestedAgentId: string | undefined,\n callerAgentId: string | undefined,\n extra: { requestInfo?: unknown }\n ): string | null {\n const fromHeader = this.getRequestHeader(extra.requestInfo, 'x-agentdev-agent-id');\n const fallbackAgentId = callerAgentId || fromHeader;\n\n if (!requestedAgentId || requestedAgentId === 'current') {\n return this.dataSource.getCurrentAgentId();\n }\n\n if (requestedAgentId === 'self') {\n return fallbackAgentId || this.dataSource.getCurrentAgentId();\n }\n\n return requestedAgentId;\n }\n\n private stringVar(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value) && typeof value[0] === 'string') return value[0];\n return undefined;\n }\n\n private getRequestHeader(requestInfo: unknown, name: string): string | undefined {\n const headers = (requestInfo as { headers?: Headers } | undefined)?.headers;\n if (!headers || typeof headers.get !== 'function') {\n return undefined;\n }\n return headers.get(name) || undefined;\n }\n}\n\nexport function createDebuggerAgentSummary(\n session: AgentSession,\n connected: boolean\n): DebuggerAgentSummary {\n return {\n id: session.id,\n name: session.name,\n createdAt: session.createdAt,\n lastActive: session.lastActive,\n connected,\n messageCount: session.messages.length,\n toolCount: session.tools.length,\n logCount: session.logs.length,\n hasHookInspector: !!session.hookInspector,\n };\n}\n\nexport function createDebuggerAgentDetails(\n session: AgentSession,\n connected: boolean\n): DebuggerAgentDetails {\n const summary = createDebuggerAgentSummary(session, connected);\n const pendingInputRequests = (session as any).pendingInputRequests as Map<string, unknown> | undefined;\n return {\n ...summary,\n projectRoot: session.projectRoot,\n currentStateType: session.currentState?.type || null,\n pendingInputCount: pendingInputRequests?.size || 0,\n hookInspector: session.hookInspector,\n };\n}\n\nexport function filterDebuggerLogs(\n logs: DebugLogEntry[],\n query: Omit<DebuggerLogQuery, 'scope' | 'currentAgentId' | 'selectedAgentId'> & { limit?: number; offset?: number }\n): DebugLogEntry[] {\n let filtered = logs;\n\n if (query.level) {\n filtered = filtered.filter(entry => entry.level === query.level);\n }\n\n if (query.namespace) {\n filtered = filtered.filter(entry => entry.namespace.includes(query.namespace!));\n }\n\n if (query.feature) {\n filtered = filtered.filter(entry => entry.context.feature === query.feature);\n }\n\n if (query.lifecycle) {\n filtered = filtered.filter(entry => entry.context.lifecycle === query.lifecycle);\n }\n\n if (typeof query.from === 'number') {\n filtered = filtered.filter(entry => entry.timestamp >= query.from!);\n }\n\n if (typeof query.to === 'number') {\n filtered = filtered.filter(entry => entry.timestamp <= query.to!);\n }\n\n if (query.search) {\n const keyword = query.search.toLowerCase();\n filtered = filtered.filter(entry => {\n const haystacks = [\n entry.message,\n entry.namespace,\n JSON.stringify(entry.data ?? ''),\n JSON.stringify(entry.context ?? {}),\n ];\n return haystacks.some(value => value.toLowerCase().includes(keyword));\n });\n }\n\n const offset = query.offset ?? 0;\n const limit = query.limit;\n return typeof limit === 'number'\n ? filtered.slice(offset, offset + limit)\n : filtered.slice(offset);\n}\n","/**\r\n * 渲染配置\r\n * 定义工具渲染模板、样式和默认映射\r\n */\r\n\r\nimport type { ToolRenderConfig, RenderTemplateItem, RenderTemplateFn } from './types.js';\r\n\r\n// ============= 类型定义 =============\r\nexport interface RenderTemplate {\r\n /** 调用时的渲染模板 */\r\n call: RenderTemplateItem;\r\n /** 结果时的渲染模板 */\r\n result: RenderTemplateItem;\r\n}\r\n\r\n// ============= 模板定义 =============\r\n/**\r\n * 渲染模板集合\r\n * 包含所有预定义的渲染模板(字符串模板和函数模板)\r\n */\r\nexport const RENDER_TEMPLATES: Record<string, RenderTemplate> = {\r\n // ----- 文件操作模板 -----\r\n 'file': {\r\n call: '<div class=\"bash-command\">Read <span class=\"file-path\">{{path}}</span></div>',\r\n result: (data) => `<pre class=\"bash-output\" style=\"max-height:300px;\">${escapeHtml(data)}</pre>`\r\n },\r\n\r\n 'file-write': {\r\n call: '<div class=\"bash-command\">Write <span class=\"file-path\">{{path}}</span></div>',\r\n result: (data) => `<div style=\"color:var(--success-color)\">✓ File written successfully</div>`\r\n },\r\n\r\n 'file-list': {\r\n call: '<div class=\"bash-command\">List <span class=\"file-path\">{{path}}</span></div>',\r\n result: (data) => {\r\n const files = (data || '').split('\\n').filter((f: string) => f);\r\n return `<div style=\"display:grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap:4px; font-family:monospace; font-size:12px;\">\r\n ${files.map((f: string) => `<div style=\"color:var(--text-primary);\">${escapeHtml(f)}</div>`).join('')}\r\n </div>`;\r\n }\r\n },\r\n\r\n // ----- 命令执行模板 -----\r\n 'command': {\r\n call: '<div class=\"bash-command\">> {{command}}</div>',\r\n result: (data) => `<pre class=\"bash-output\">${escapeHtml(data)}</pre>`\r\n },\r\n\r\n // ----- 网络请求模板 -----\r\n 'web': {\r\n call: '<div>GET <a href=\"{{url}}\" target=\"_blank\" style=\"color:var(--accent-color)\">{{url}}</a></div>',\r\n result: (data) => `<div style=\"font-size:12px; opacity:0.8;\">Fetched ${String(data).length} chars</div>`\r\n },\r\n\r\n // ----- 数学计算模板 -----\r\n 'math': {\r\n call: '<div class=\"bash-command\">{{expression}}</div>',\r\n result: (data) => `<div class=\"bash-command\" style=\"color:#d2a8ff\">= ${escapeHtml(data)}</div>`\r\n },\r\n\r\n // ----- 内置类型 -----\r\n 'json': {\r\n call: (args) => `<pre style=\"margin:0; font-size:12px;\">${escapeHtml(JSON.stringify(args, null, 2))}</pre>`,\r\n result: (data) => {\r\n const displayData = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;\r\n return `<pre class=\"bash-output\">${escapeHtml(displayData)}</pre>`;\r\n }\r\n },\r\n\r\n // ----- Skills 工具 -----\r\n 'skill': {\r\n call: '<div class=\"bash-command\">Skill <span class=\"file-path\">{{skill}}</span></div>',\r\n result: (data) => `<pre class=\"bash-output\" style=\"max-height:400px;\">${escapeHtml(data)}</pre>`\r\n },\r\n\r\n // ----- Agent Spawn -----\r\n 'agent-spawn': {\r\n call: '<div class=\"bash-command\">Spawn <span class=\"pattern\">{{type}}</span> agent: {{instruction}}</div>',\r\n result: (data) => {\r\n if (data.error) {\r\n return `<div style=\"color:var(--error-color)\">✗ ${data.error}</div>`;\r\n }\r\n return `<div style=\"color:var(--success-color)\">✓ Agent ${data.agentId} (${data.type}) completed</div>`;\r\n }\r\n },\r\n\r\n // ----- Agent List -----\r\n 'agent-list': {\r\n call: '<div class=\"bash-command\">List agents</div>',\r\n result: (data) => {\r\n if (!data.agents || data.agents.length === 0) {\r\n return `<div style=\"color:var(--warning-color)\">No agents found</div>`;\r\n }\r\n return `<div style=\"font-size:12px; color:var(--text-secondary);\">Total: ${data.total} | Running: ${data.running}</div>`;\r\n }\r\n },\r\n\r\n // ----- Agent Send -----\r\n 'agent-send': {\r\n call: '<div class=\"bash-command\">Send to <span class=\"pattern\">{{agentId}}</span></div>',\r\n result: (data) => {\r\n if (data.error) {\r\n return `<div style=\"color:var(--error-color)\">✗ ${data.error}</div>`;\r\n }\r\n return `<div style=\"color:var(--success-color)\">✓ Message sent</div>`;\r\n }\r\n },\r\n\r\n // ----- Agent Close -----\r\n 'agent-close': {\r\n call: '<div class=\"bash-command\">Close <span class=\"pattern\">{{agentId}}</span></div>',\r\n result: (data) => {\r\n if (data.error) {\r\n return `<div style=\"color:var(--error-color)\">✗ ${data.error}</div>`;\r\n }\r\n return `<div style=\"color:var(--success-color)\">✓ ${data.message || 'Agent closed'}</div>`;\r\n }\r\n },\r\n\r\n // ----- Safe Trash Delete -----\r\n 'trash-delete': {\r\n call: (args) => {\r\n const paths = args.paths || [];\r\n const pathList = Array.isArray(paths) ? paths : [paths];\r\n const displayPaths = pathList.slice(0, 3).map((p: string) => escapeHtml(p)).join(', ');\r\n const more = pathList.length > 3 ? ` +${pathList.length - 3} more` : '';\r\n return `<div class=\"bash-command\">Safe delete <span class=\"file-path\">${displayPaths}${more}</span></div>`;\r\n },\r\n result: (data) => {\r\n const movedCount = data.moved_count || 0;\r\n const failed = data.failed || [];\r\n const failedCount = failed.length;\r\n\r\n if (movedCount > 0 && failedCount === 0) {\r\n return `<div style=\"color:var(--success-color)\">✓ Moved ${movedCount} item(s) to trash</div>`;\r\n }\r\n if (movedCount > 0) {\r\n return `<div style=\"color:var(--warning-color)\">⚠ Moved ${movedCount}, ${failedCount} failed</div>`;\r\n }\r\n return `<div style=\"color:var(--text-secondary)\">No files moved</div>`;\r\n }\r\n },\r\n\r\n // ----- Safe Trash List -----\r\n 'trash-list': {\r\n call: '<div class=\"bash-command\">List trash</div>',\r\n result: (data) => {\r\n const total = data.total || 0;\r\n const files = data.files || [];\r\n if (total === 0) {\r\n return `<div style=\"color:var(--text-secondary)\">Trash is empty</div>`;\r\n }\r\n return `<div style=\"font-size:12px; color:var(--text-secondary);\">${total} item(s) in trash</div>`;\r\n }\r\n },\r\n\r\n // ----- Safe Trash Restore -----\r\n 'trash-restore': {\r\n call: (args) => `<div class=\"bash-command\">Restore <span class=\"pattern\">${escapeHtml(args.target)}</span></div>`,\r\n result: (data) => {\r\n const restored = data.restored || [];\r\n const failed = data.failed || [];\r\n const restoredCount = restored.length;\r\n const failedCount = failed.length;\r\n\r\n if (restoredCount > 0 && failedCount === 0) {\r\n return `<div style=\"color:var(--success-color)\">✓ Restored ${restoredCount} item(s)</div>`;\r\n }\r\n if (restoredCount > 0 || failedCount > 0) {\r\n return `<div style=\"color:var(--warning-color)\">Restored ${restoredCount}, ${failedCount} failed</div>`;\r\n }\r\n return `<div style=\"color:var(--text-secondary)\">Nothing restored</div>`;\r\n }\r\n },\r\n} as const;\r\n\r\n// ============= 默认映射 =============\r\n/**\r\n * 系统工具的默认渲染模板映射\r\n * 工具名称 -> 模板名称\r\n */\r\nexport const SYSTEM_RENDER_MAP: Record<string, string> = {\r\n // ===== 系统工具 =====\r\n // 文件系统工具(旧版,已废弃)\r\n 'read_file': 'file',\r\n 'write_file': 'file-write',\r\n 'list_directory': 'file-list',\r\n\r\n // Shell 工具\r\n 'run_shell_command': 'command',\r\n 'bash': 'command',\r\n 'shell': 'command',\r\n\r\n // Safe Trash 工具\r\n 'safe_trash_delete': 'trash-delete',\r\n 'safe_trash_list': 'trash-list',\r\n 'safe_trash_restore': 'trash-restore',\r\n\r\n // Web 工具\r\n 'web_fetch': 'web',\r\n\r\n // Math 工具\r\n 'calculator': 'math',\r\n\r\n // Skills 工具\r\n 'invoke_skill': 'skill',\r\n\r\n // Agent 工具\r\n 'spawn_agent': 'agent-spawn',\r\n 'list_agents': 'agent-list',\r\n 'send_to_agent': 'agent-send',\r\n 'close_agent': 'agent-close',\r\n} as const;\r\n\r\n// ============= 工具显示名称映射 =============\r\n/**\r\n * 工具显示名称(用于UI展示)\r\n */\r\nexport const TOOL_DISPLAY_NAMES: Record<string, string> = {\r\n // ===== 系统工具 =====\r\n 'run_shell_command': 'Bash',\r\n 'bash': 'Bash',\r\n 'shell': 'Bash',\r\n 'read_file': 'Read File',\r\n 'write_file': 'Write File',\r\n 'list_directory': 'LS',\r\n 'web_fetch': 'Web',\r\n 'calculator': 'Calc',\r\n 'invoke_skill': 'Invoke Skill',\r\n 'spawn_agent': 'Spawn Agent',\r\n 'list_agents': 'List Agents',\r\n 'send_to_agent': 'Send to Agent',\r\n 'close_agent': 'Close Agent',\r\n\r\n // ===== Safe Trash 工具 =====\r\n 'safe_trash_delete': 'Safe Delete',\r\n 'safe_trash_list': 'Trash List',\r\n 'safe_trash_restore': 'Restore',\r\n\r\n // ===== Opencode 工具 =====\r\n 'read': 'Read',\r\n 'write': 'Write',\r\n 'edit': 'Edit',\r\n 'glob': 'Glob',\r\n 'grep': 'Grep',\r\n 'ls': 'LS',\r\n} as const;\r\n\r\n// ============= 辅助函数 =============\r\n/**\r\n * HTML 转义\r\n */\r\nfunction escapeHtml(text: any): string {\r\n const str = String(text);\r\n const map: Record<string, string> = {\r\n '&': '&amp;',\r\n '<': '&lt;',\r\n '>': '&gt;',\r\n '\"': '&quot;',\r\n \"'\": '&#39;'\r\n };\r\n return str.replace(/[&<>\"']/g, m => map[m]);\r\n}\r\n\r\n// ============= 模板引擎 =============\r\n/**\r\n * 简单的字符串模板插值\r\n * 支持 {{key}} 语法\r\n */\r\nexport function interpolateTemplate(template: string, data: Record<string, any>): string {\r\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => {\r\n const value = data[key];\r\n return value !== undefined ? String(value) : `{{${key}}}`;\r\n });\r\n}\r\n\r\n/**\r\n * 应用渲染模板\r\n */\r\nexport function applyTemplate(\r\n template: RenderTemplateItem,\r\n data: Record<string, any>,\r\n success = true\r\n): string {\r\n if (typeof template === 'function') {\r\n return template(data, success);\r\n }\r\n return interpolateTemplate(template, data);\r\n}\r\n\r\n// ============= 动态模板加载 =============\r\n/**\r\n * 从文件加载渲染模板\r\n * @param toolPath 工具路径(相对于项目根目录)\r\n */\r\nexport async function loadRenderTemplate(\r\n toolPath: string\r\n): Promise<RenderTemplate | undefined> {\r\n try {\r\n // 构建模板文件路径:工具目录 + .render.ts\r\n const templatePath = toolPath + '.render.ts';\r\n\r\n // 动态导入模板模块\r\n const module = await import(templatePath);\r\n\r\n // 模块应导出 default 或 render 函数返回 RenderTemplate\r\n if (module.default && typeof module.default === 'object') {\r\n return module.default as RenderTemplate;\r\n }\r\n if (module.render && typeof module.render === 'function') {\r\n return await module.render();\r\n }\r\n\r\n return undefined;\r\n } catch {\r\n // 文件不存在或导入失败,返回 undefined\r\n return undefined;\r\n }\r\n}\r\n\r\n// ============= 导出合并函数 =============\r\n/**\r\n * 获取工具的渲染配置(合并默认值)\r\n */\r\nexport function getToolRenderConfig(\r\n toolName: string,\r\n customRender?: ToolRenderConfig\r\n): ToolRenderConfig {\r\n // 优先级:自定义配置 > 系统默认映射 > 'json'\r\n const systemDefault = SYSTEM_RENDER_MAP[toolName];\r\n const defaultTemplate = systemDefault || 'json';\r\n\r\n return {\r\n call: customRender?.call || defaultTemplate,\r\n result: customRender?.result || defaultTemplate,\r\n };\r\n}\r\n\r\n/**\r\n * 获取工具的渲染模板\r\n */\r\nexport function getToolRenderTemplate(toolName: string, customRender?: ToolRenderConfig): RenderTemplate {\r\n const config = getToolRenderConfig(toolName, customRender);\r\n\r\n // 处理 call 模板\r\n let callTemplate: RenderTemplateItem;\r\n if (typeof config.call === 'object' && config.call !== null) {\r\n // 内联模板\r\n callTemplate = config.call.call;\r\n } else {\r\n // 字符串引用\r\n const callTemplateName = config.call || 'json';\r\n callTemplate = RENDER_TEMPLATES[callTemplateName]?.call || RENDER_TEMPLATES['json'].call;\r\n }\r\n\r\n // 处理 result 模板\r\n let resultTemplate: RenderTemplateItem;\r\n if (typeof config.result === 'object' && config.result !== null) {\r\n // 内联模板\r\n resultTemplate = config.result.result;\r\n } else {\r\n // 字符串引用\r\n const resultTemplateName = config.result || 'json';\r\n resultTemplate = RENDER_TEMPLATES[resultTemplateName]?.result || RENDER_TEMPLATES['json'].result;\r\n }\r\n\r\n return {\r\n call: callTemplate,\r\n result: resultTemplate,\r\n };\r\n}\r\n\r\n/**\r\n * 获取工具显示名称\r\n */\r\nexport function getToolDisplayName(toolName: string): string {\r\n return TOOL_DISPLAY_NAMES[toolName] || toolName;\r\n}\r\n","/**\n * Viewer HTML Template - 调试器前端 HTML 模板\n *\n * 这个文件原本是 viewer-worker.ts 中的 getHtml() 方法\n * 已被提取为独立模块,便于维护和编辑\n */\n\n/**\n * 生成调试器前端页面的完整 HTML\n * @param port - HTTP 服务器端口号\n * @returns 完整的 HTML 字符串\n */\nexport function generateViewerHtml(port: number): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Agent Debugger</title>\n <script src=\"https://cdn.jsdelivr.net/npm/marked/marked.min.js\"></script>\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-dark.min.css\">\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css\">\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js\"></script>\n\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js\"></script>\n\n <style>\n :root {\n --bg-color: #000000;\n --sidebar-bg: #0a0a0a;\n --header-bg: #0a0a0a;\n --panel-bg: #070707;\n --border-color: #222;\n --text-primary: #ededed;\n --text-secondary: #888;\n --text-muted: #444;\n --accent-color: #ededed;\n --code-accent: #58a6ff;\n --user-msg-bg: #1a1a1a;\n --assistant-msg-bg: #000000;\n --tool-msg-bg: #050505;\n --success-color: #198754;\n --error-color: #dc3545;\n --hover-bg: #1f1f1f;\n --active-bg: #2a2a2a;\n --warning-color: #ffc107;\n --bg-secondary: var(--hover-bg);\n --scrollbar-thumb: #333;\n --scrollbar-thumb-hover: #555;\n --input-card-bg: #090909;\n --input-card-border: #222;\n --shadow-color: rgba(0, 0, 0, 0.18);\n --shadow-strong: rgba(0, 0, 0, 0.8);\n --status-text-on-color: #fff;\n }\n\n body[data-theme=\"light\"] {\n --bg-color: #fafafa;\n --sidebar-bg: #f4f4f4;\n --header-bg: #f4f4f4;\n --panel-bg: #f7f7f7;\n --border-color: #d8d8d8;\n --text-primary: #121212;\n --text-secondary: #666;\n --text-muted: #8a8a8a;\n --accent-color: #121212;\n --user-msg-bg: #efefef;\n --assistant-msg-bg: #fafafa;\n --tool-msg-bg: #f1f1f1;\n --hover-bg: #eaeaea;\n --active-bg: #e2e2e2;\n --bg-secondary: var(--hover-bg);\n --scrollbar-thumb: #c0c0c0;\n --scrollbar-thumb-hover: #a7a7a7;\n --input-card-bg: #ffffff;\n --input-card-border: #d8d8d8;\n --shadow-color: rgba(0, 0, 0, 0.08);\n --shadow-strong: rgba(0, 0, 0, 0.14);\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n ::-webkit-scrollbar { width: 8px; height: 8px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 4px; }\n ::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif;\n background-color: var(--bg-color);\n color: var(--text-primary);\n height: 100vh;\n display: flex;\n font-size: 14px;\n overflow: hidden;\n }\n\n /* Sidebar */\n .sidebar {\n width: 260px;\n background-color: var(--sidebar-bg);\n border-right: 1px solid var(--border-color);\n display: flex;\n flex-direction: column;\n transition: width 0.3s ease, transform 0.3s ease;\n flex-shrink: 0;\n overflow: hidden;\n }\n \n .sidebar.collapsed {\n width: 0;\n border-right: none;\n }\n\n .sidebar-header {\n padding: 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid var(--border-color);\n height: 56px;\n }\n \n .sidebar-title { font-weight: 600; font-size: 16px; }\n\n .agent-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .agent-item {\n padding: 10px 12px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n gap: 4px;\n transition: background-color 0.2s;\n color: var(--text-secondary);\n }\n\n .agent-item.disconnected {\n opacity: 0.8;\n }\n \n .agent-item:hover {\n background-color: var(--hover-bg);\n color: var(--text-primary);\n }\n \n .agent-item.active {\n background-color: var(--active-bg);\n color: var(--text-primary);\n font-weight: 500;\n }\n\n .agent-name { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .agent-meta { font-size: 11px; opacity: 0.6; }\n .agent-status {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n .agent-status-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--success-color);\n }\n .agent-item.disconnected .agent-status-dot {\n background: var(--error-color);\n }\n\n .context-menu {\n position: fixed;\n min-width: 160px;\n background: var(--sidebar-bg);\n border: 1px solid var(--border-color);\n border-radius: 8px;\n box-shadow: 0 10px 30px var(--shadow-color);\n padding: 6px;\n z-index: 1000;\n display: none;\n }\n .context-menu.open {\n display: block;\n }\n .context-menu-item {\n width: 100%;\n border: none;\n background: transparent;\n color: var(--text-primary);\n text-align: left;\n padding: 9px 10px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n }\n .context-menu-item:hover {\n background: var(--hover-bg);\n }\n .context-menu-item.danger {\n color: var(--error-color);\n }\n .context-menu-item:disabled {\n color: var(--text-secondary);\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n /* Main Content */\n .main-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-width: 0;\n background-color: var(--bg-color);\n position: relative; /* For positioning input container */\n }\n\n .right-workspace {\n display: flex;\n flex-shrink: 0;\n height: 100vh;\n min-width: 56px;\n }\n\n .feature-panel {\n width: 0;\n background: var(--panel-bg);\n border-left: 1px solid transparent;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n transition: width 0.24s ease, border-color 0.24s ease;\n position: relative;\n flex-shrink: 0;\n }\n\n .feature-panel.open {\n width: var(--feature-panel-width, 320px);\n border-left-color: var(--border-color);\n }\n\n .feature-panel-resizer {\n position: absolute;\n top: 0;\n left: 0;\n width: 6px;\n height: 100%;\n cursor: col-resize;\n z-index: 2;\n }\n\n .feature-panel-resizer::after {\n content: '';\n position: absolute;\n left: 2px;\n top: 0;\n width: 1px;\n height: 100%;\n background: rgba(255, 255, 255, 0.08);\n }\n\n .feature-panel-header {\n height: 56px;\n padding: 0 16px 0 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n border-bottom: 1px solid var(--border-color);\n flex-shrink: 0;\n }\n\n .feature-panel-title {\n font-size: 14px;\n font-weight: 600;\n letter-spacing: 0.01em;\n }\n\n .feature-panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 18px 20px 24px 22px;\n position: relative;\n }\n\n .feature-panel-empty {\n display: flex;\n flex-direction: column;\n gap: 10px;\n color: var(--text-secondary);\n line-height: 1.6;\n }\n\n .feature-panel-section {\n padding: 13px 15px;\n border: 1px solid var(--border-color);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.02);\n }\n\n .feature-panel-section-title {\n color: var(--text-primary);\n font-size: 13px;\n font-weight: 600;\n margin-bottom: 6px;\n }\n\n .hooks-panel {\n display: flex;\n flex-direction: column;\n gap: 18px;\n }\n\n .hooks-hero {\n position: relative;\n overflow: hidden;\n padding: 18px;\n border: 1px solid var(--border-color);\n border-radius: 16px;\n background:\n radial-gradient(circle at top right, rgba(255, 120, 70, 0.20), transparent 34%),\n radial-gradient(circle at bottom left, rgba(87, 180, 255, 0.16), transparent 36%),\n linear-gradient(135deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));\n box-shadow: 0 20px 50px var(--shadow-color);\n }\n\n .hooks-hero::after {\n content: '';\n position: absolute;\n inset: 0;\n background-image:\n linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),\n linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px);\n background-size: 22px 22px;\n pointer-events: none;\n opacity: 0.18;\n }\n\n .hooks-hero > * {\n position: relative;\n z-index: 1;\n }\n\n .hooks-kicker {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 11px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #ffb88d;\n margin-bottom: 10px;\n }\n\n .hooks-kicker::before {\n content: '';\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: linear-gradient(135deg, #ff9b62, #ffd27f);\n box-shadow: 0 0 18px rgba(255, 155, 98, 0.45);\n }\n\n .hooks-hero-title {\n font-size: 21px;\n line-height: 1.1;\n font-weight: 700;\n margin-bottom: 8px;\n color: var(--text-primary);\n }\n\n .hooks-hero-subtitle {\n color: var(--text-secondary);\n line-height: 1.65;\n max-width: 34ch;\n margin-bottom: 16px;\n }\n\n .hooks-stats {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n }\n\n .hooks-stat {\n padding: 11px 12px;\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.02);\n border: 1px solid var(--border-color);\n }\n\n body[data-theme=\"light\"] .hooks-stat {\n background: rgba(255, 255, 255, 0.8);\n }\n\n .hooks-stat-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-secondary);\n margin-bottom: 6px;\n }\n\n .hooks-stat-value {\n font-size: 20px;\n font-weight: 700;\n color: var(--text-primary);\n }\n\n .hooks-summary {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border: 1px solid var(--border-color);\n border-radius: 14px;\n background: rgba(255, 255, 255, 0.02);\n }\n\n .hooks-summary-top {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n }\n\n .hooks-summary-title {\n font-size: 15px;\n font-weight: 700;\n color: var(--text-primary);\n }\n\n .hooks-summary-meta {\n font-size: 12px;\n color: var(--text-secondary);\n }\n\n .hooks-strip {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n\n .hooks-chip {\n appearance: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 10px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.03);\n color: var(--text-secondary);\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n\n .hooks-chip.active {\n color: var(--text-primary);\n background: rgba(255, 255, 255, 0.06);\n }\n\n .hooks-chip strong {\n color: var(--text-primary);\n font-weight: 600;\n }\n\n .hooks-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n }\n\n .overview-doc {\n padding: 17px 19px;\n border-radius: 14px;\n }\n\n .overview-doc .markdown-body {\n font-size: 12.5px !important;\n line-height: 1.8 !important;\n }\n\n .overview-doc .markdown-body p {\n margin-bottom: 13px !important;\n }\n\n .overview-doc .markdown-body pre {\n margin: 14px 0 !important;\n font-size: 12px !important;\n }\n\n .hooks-section-header {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: 12px;\n }\n\n .hooks-section-title {\n font-size: 13px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--text-primary);\n }\n\n .hooks-section-meta {\n font-size: 12px;\n color: var(--text-secondary);\n }\n\n .feature-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n }\n\n .overview-usage-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n }\n\n .context-chip-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n }\n\n .context-chip {\n padding: 14px 15px;\n border-radius: 16px;\n border: 1px solid var(--border-color);\n background:\n linear-gradient(135deg, rgba(91, 192, 255, 0.08), rgba(255, 156, 100, 0.08)),\n rgba(255, 255, 255, 0.03);\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-height: 96px;\n }\n\n body[data-theme=\"light\"] .context-chip {\n background:\n linear-gradient(135deg, rgba(91, 192, 255, 0.12), rgba(255, 156, 100, 0.10)),\n rgba(255, 255, 255, 0.92);\n }\n\n .context-chip-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-secondary);\n }\n\n .context-chip-value {\n font-size: 22px;\n line-height: 1;\n font-weight: 800;\n color: var(--text-primary);\n }\n\n .context-chip-meta {\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.5;\n }\n\n .usage-card {\n padding: 14px;\n border-radius: 16px;\n border: 1px solid var(--border-color);\n background:\n linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02)),\n rgba(255, 255, 255, 0.02);\n box-shadow: 0 10px 28px rgba(0, 0, 0, 0.12);\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-height: 184px;\n }\n\n body[data-theme=\"light\"] .usage-card {\n background:\n linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(250, 250, 250, 0.88)),\n rgba(255, 255, 255, 0.9);\n box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);\n }\n\n .usage-card-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n }\n\n .usage-card-title {\n font-size: 13px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--text-primary);\n }\n\n .usage-card-subtitle {\n font-size: 11px;\n color: var(--text-secondary);\n margin-top: 4px;\n }\n\n .usage-card-total {\n font-size: 24px;\n line-height: 1;\n font-weight: 800;\n color: var(--text-primary);\n white-space: nowrap;\n }\n\n .usage-bar {\n display: flex;\n width: 100%;\n height: 10px;\n border-radius: 999px;\n overflow: hidden;\n background: rgba(255, 255, 255, 0.07);\n border: 1px solid rgba(255, 255, 255, 0.06);\n }\n\n .usage-bar-fill {\n height: 100%;\n }\n\n .usage-bar-fill.input {\n background: linear-gradient(90deg, #5bc0ff, #8be8ff);\n }\n\n .usage-bar-fill.output {\n background: linear-gradient(90deg, #ff9c64, #ffd17b);\n }\n\n .usage-split-legend {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 14px;\n font-size: 11px;\n color: var(--text-secondary);\n }\n\n .usage-split-legend span {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n .legend-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n display: inline-block;\n }\n\n .legend-dot.input {\n background: #73d6ff;\n }\n\n .legend-dot.output {\n background: #ffb576;\n }\n\n .usage-stat-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n }\n\n .usage-stat-cell {\n padding: 10px 11px;\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(255, 255, 255, 0.05);\n }\n\n body[data-theme=\"light\"] .usage-stat-cell {\n background: rgba(248, 250, 252, 0.9);\n border-color: rgba(15, 23, 42, 0.06);\n }\n\n .usage-stat-cell-label {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-secondary);\n margin-bottom: 5px;\n }\n\n .usage-stat-cell-value {\n font-size: 16px;\n font-weight: 700;\n color: var(--text-primary);\n }\n\n .rate-ring-card {\n display: grid;\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 16px;\n min-height: 92px;\n }\n\n .rate-ring {\n width: 92px;\n height: 92px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n background:\n conic-gradient(#7dd3a4 calc(var(--ring-percent) * 1%), rgba(255,255,255,0.08) 0);\n position: relative;\n }\n\n .rate-ring::after {\n content: '';\n position: absolute;\n inset: 10px;\n border-radius: 50%;\n background: var(--panel-bg);\n border: 1px solid rgba(255, 255, 255, 0.06);\n }\n\n body[data-theme=\"light\"] .rate-ring::after {\n background: #ffffff;\n border-color: rgba(15, 23, 42, 0.06);\n }\n\n .rate-ring-inner {\n position: relative;\n z-index: 1;\n text-align: center;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .rate-ring-value {\n font-size: 18px;\n font-weight: 800;\n color: var(--text-primary);\n line-height: 1;\n }\n\n .rate-ring-label {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-secondary);\n }\n\n .rate-ring-meta {\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.6;\n }\n\n .hooks-collapsible {\n border: 1px solid var(--border-color);\n border-radius: 14px;\n overflow: hidden;\n background: rgba(255, 255, 255, 0.02);\n }\n\n .hooks-collapsible > summary {\n list-style: none;\n cursor: pointer;\n }\n\n .hooks-collapsible > summary::-webkit-details-marker {\n display: none;\n }\n\n .hooks-collapsible-body {\n padding: 0 12px 12px 12px;\n border-top: 1px solid var(--border-color);\n }\n\n .feature-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .feature-card {\n padding: 10px 12px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.02);\n cursor: pointer;\n transition: border-color 0.18s ease, transform 0.18s ease, background 0.18s ease;\n }\n\n .feature-card:hover {\n border-color: rgba(255, 255, 255, 0.18);\n background: rgba(255, 255, 255, 0.04);\n transform: translateY(-1px);\n }\n\n .feature-card-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n\n .feature-card-main {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n }\n\n .feature-card-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: #7dd3a4;\n flex-shrink: 0;\n }\n\n .feature-card-name {\n font-weight: 700;\n color: var(--text-primary);\n }\n\n .feature-card-file {\n font-size: 11px;\n color: var(--text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .feature-badge {\n padding: 3px 7px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n border: 1px solid var(--border-color);\n color: var(--text-primary);\n background: rgba(255, 255, 255, 0.05);\n }\n\n .feature-badge.status-enabled {\n color: #14532d;\n background: rgba(134, 239, 172, 0.92);\n border-color: rgba(74, 222, 128, 0.9);\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);\n }\n\n .feature-badge.status-partial {\n color: #7c2d12;\n background: rgba(253, 186, 116, 0.92);\n border-color: rgba(251, 146, 60, 0.9);\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);\n }\n\n .feature-badge.status-disabled {\n color: #7f1d1d;\n background: rgba(252, 165, 165, 0.9);\n border-color: rgba(248, 113, 113, 0.88);\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .feature-badge.status-removed {\n color: #fff1f2;\n background: rgba(190, 18, 60, 0.72);\n border-color: rgba(225, 29, 72, 0.75);\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .feature-badge.status-superseded {\n color: #f5f5f4;\n background: rgba(120, 113, 108, 0.72);\n border-color: rgba(168, 162, 158, 0.75);\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n body[data-theme=\"light\"] .feature-badge.status-enabled {\n color: #166534;\n background: rgba(220, 252, 231, 1);\n }\n\n body[data-theme=\"light\"] .feature-badge.status-partial {\n color: #9a3412;\n background: rgba(255, 237, 213, 1);\n }\n\n body[data-theme=\"light\"] .feature-badge.status-disabled {\n color: #991b1b;\n background: rgba(254, 226, 226, 1);\n }\n\n body[data-theme=\"light\"] .feature-badge.status-removed {\n color: #881337;\n background: rgba(255, 228, 230, 1);\n }\n\n body[data-theme=\"light\"] .feature-badge.status-superseded {\n color: #57534e;\n background: rgba(231, 229, 228, 1);\n }\n\n .feature-card-detail {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n color: var(--text-secondary);\n font-size: 12px;\n margin-top: 7px;\n }\n\n .feature-detail-shell {\n position: static;\n min-height: 100%;\n }\n\n .feature-detail-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 18px;\n background: rgba(5, 7, 12, 0.86);\n backdrop-filter: blur(2px);\n z-index: 20;\n }\n\n body[data-theme=\"light\"] .feature-detail-overlay {\n background: rgba(18, 20, 26, 0.72);\n }\n\n .feature-detail-window {\n width: min(100%, 720px);\n max-height: min(100%, 700px);\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 14px;\n border-radius: 18px;\n border: 1px solid var(--border-color);\n background: var(--panel-bg);\n box-shadow: 0 28px 70px rgba(0, 0, 0, 0.34);\n padding: 18px;\n }\n\n .feature-detail-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n }\n\n .feature-detail-title {\n font-size: 18px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 6px;\n }\n\n .feature-detail-subtitle {\n font-size: 12px;\n line-height: 1.7;\n color: var(--text-secondary);\n }\n\n .feature-detail-close {\n width: 32px;\n height: 32px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.04);\n color: var(--text-primary);\n cursor: pointer;\n flex-shrink: 0;\n }\n\n .feature-detail-stats {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 10px;\n }\n\n .feature-detail-stat {\n padding: 10px 11px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.02);\n }\n\n .feature-detail-stat-label {\n font-size: 11px;\n color: var(--text-secondary);\n margin-bottom: 5px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n }\n\n .feature-detail-stat-value {\n font-size: 14px;\n font-weight: 700;\n color: var(--text-primary);\n }\n\n .feature-tool-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n margin-top: 12px;\n }\n\n .feature-tool-card {\n padding: 11px 12px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.02);\n }\n\n .feature-tool-top {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 6px;\n }\n\n .feature-tool-name {\n font-size: 13px;\n font-weight: 700;\n color: var(--text-primary);\n }\n\n .feature-tool-desc {\n font-size: 12px;\n line-height: 1.7;\n color: var(--text-secondary);\n }\n\n .feature-tool-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 8px;\n }\n\n .feature-tool-pill {\n padding: 3px 7px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n font-size: 10px;\n color: var(--text-secondary);\n background: rgba(255, 255, 255, 0.03);\n }\n\n .hook-lifecycle-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .hook-lifecycle-card {\n border: 1px solid var(--border-color);\n border-radius: 14px;\n overflow: hidden;\n background: rgba(255, 255, 255, 0.02);\n }\n\n .hook-lifecycle-card[open] {\n background: rgba(255, 255, 255, 0.03);\n }\n\n .hook-lifecycle-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 13px 15px;\n cursor: pointer;\n list-style: none;\n }\n\n .hook-lifecycle-head::-webkit-details-marker {\n display: none;\n }\n\n .hook-lifecycle-name {\n display: flex;\n align-items: center;\n gap: 10px;\n color: var(--text-primary);\n font-weight: 700;\n }\n\n .hook-lifecycle-icon {\n width: 24px;\n height: 24px;\n border-radius: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 800;\n color: #111;\n background: linear-gradient(135deg, #f0d896, #e59d73);\n }\n\n .hook-lifecycle-type {\n font-size: 11px;\n color: var(--text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n\n .hook-call-chain {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px 12px 12px;\n border-top: 1px solid var(--border-color);\n }\n\n .hook-step {\n display: flex;\n gap: 10px;\n padding-top: 8px;\n }\n\n .hook-step-order {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n background: rgba(255, 255, 255, 0.08);\n color: var(--text-primary);\n flex-shrink: 0;\n margin-top: 2px;\n }\n\n .hook-step-card {\n flex: 1;\n padding: 10px 11px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.018);\n }\n\n .hook-step-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 6px;\n }\n\n .hook-step-feature {\n font-size: 12px;\n color: var(--text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n\n .hook-step-kind {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 3px 7px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n color: var(--text-primary);\n background: rgba(255,255,255,0.05);\n }\n\n .hook-step-method {\n font-size: 14px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 4px;\n word-break: break-word;\n }\n\n .hook-step-location {\n font-size: 12px;\n color: var(--text-secondary);\n word-break: break-all;\n }\n\n .hook-step-notes {\n margin-top: 8px;\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.5;\n }\n\n .hook-lifecycle-toggle {\n color: var(--text-secondary);\n font-size: 13px;\n flex-shrink: 0;\n transition: transform 0.18s ease;\n }\n\n .hook-lifecycle-card[open] .hook-lifecycle-toggle {\n transform: rotate(90deg);\n }\n\n .log-toolbar {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border: 1px solid var(--border-color);\n border-radius: 14px;\n background: rgba(255, 255, 255, 0.02);\n }\n\n .log-panel {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .mcp-panel {\n display: flex;\n flex-direction: column;\n gap: 18px;\n }\n\n .mcp-hero {\n position: relative;\n overflow: hidden;\n padding: 18px;\n border: 1px solid var(--border-color);\n border-radius: 16px;\n background:\n radial-gradient(circle at top right, rgba(71, 195, 160, 0.22), transparent 34%),\n radial-gradient(circle at bottom left, rgba(80, 133, 255, 0.16), transparent 36%),\n linear-gradient(135deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));\n box-shadow: 0 20px 50px var(--shadow-color);\n }\n\n .mcp-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n }\n\n .mcp-stat {\n padding: 12px 13px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.03);\n }\n\n .mcp-stat-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-secondary);\n margin-bottom: 6px;\n }\n\n .mcp-stat-value {\n font-size: 16px;\n font-weight: 700;\n color: var(--text-primary);\n line-height: 1.4;\n word-break: break-all;\n }\n\n .mcp-status-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 7px 10px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n font-size: 12px;\n color: var(--text-primary);\n background: rgba(255, 255, 255, 0.04);\n margin-top: 12px;\n }\n\n .mcp-status-pill::before {\n content: '';\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: #4ade80;\n box-shadow: 0 0 16px rgba(74, 222, 128, 0.4);\n }\n\n .mcp-code {\n padding: 12px 14px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(0, 0, 0, 0.22);\n font-size: 12px;\n line-height: 1.65;\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .mcp-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .mcp-item {\n padding: 11px 12px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.02);\n }\n\n .mcp-item-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 6px;\n }\n\n .mcp-item-name {\n font-weight: 700;\n color: var(--text-primary);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n }\n\n .mcp-item-type {\n font-size: 11px;\n color: var(--text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n\n .mcp-item-desc {\n font-size: 12px;\n line-height: 1.7;\n color: var(--text-secondary);\n }\n\n .log-filter-row {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n }\n\n .log-filter-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-secondary);\n min-width: 54px;\n }\n\n .log-chip-group {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n }\n\n .log-chip {\n appearance: none;\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.03);\n color: var(--text-secondary);\n border-radius: 999px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;\n }\n\n .log-chip:hover,\n .log-chip.active {\n color: var(--text-primary);\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(255, 255, 255, 0.18);\n }\n\n .log-input,\n .log-select {\n border: 1px solid var(--border-color);\n background: rgba(255, 255, 255, 0.03);\n color: var(--text-primary);\n border-radius: 10px;\n padding: 8px 10px;\n font-size: 12px;\n min-height: 34px;\n font-family: inherit;\n outline: none;\n }\n\n .log-input:focus,\n .log-select:focus {\n border-color: rgba(88, 166, 255, 0.45);\n box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.12);\n }\n\n .log-input {\n flex: 1;\n min-width: 140px;\n }\n\n .log-select {\n min-width: 130px;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n padding-right: 34px;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--text-secondary) 50%),\n linear-gradient(135deg, var(--text-secondary) 50%, transparent 50%);\n background-position:\n calc(100% - 18px) calc(50% - 1px),\n calc(100% - 12px) calc(50% - 1px);\n background-size: 6px 6px, 6px 6px;\n background-repeat: no-repeat;\n }\n\n .log-select option {\n background: var(--panel-bg);\n color: var(--text-primary);\n }\n\n .log-select option:checked,\n .log-select option:hover {\n background: var(--hover-bg);\n color: var(--text-primary);\n }\n\n .log-summary {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n color: var(--text-secondary);\n font-size: 12px;\n }\n\n .log-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .log-card {\n border: 1px solid var(--border-color);\n border-radius: 14px;\n background: rgba(255, 255, 255, 0.02);\n overflow: hidden;\n }\n\n .log-card-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 11px 13px 8px 13px;\n border-bottom: 1px solid rgba(255,255,255,0.04);\n }\n\n .log-card-main {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex-wrap: wrap;\n }\n\n .log-level {\n font-size: 10px;\n font-weight: 800;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 3px 7px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n color: var(--text-primary);\n background: rgba(255,255,255,0.04);\n }\n\n .log-level.debug, .log-level.trace {\n color: #7cc5ff;\n }\n\n .log-level.info {\n color: #7dd3a4;\n }\n\n .log-level.warn {\n color: #f6c96c;\n }\n\n .log-level.error {\n color: #ff8f8f;\n }\n\n .log-namespace {\n font-size: 12px;\n color: var(--text-secondary);\n font-family: \"Fira Code\", \"Cascadia Code\", \"JetBrains Mono\", ui-monospace, monospace;\n }\n\n .log-timestamp {\n font-size: 11px;\n color: var(--text-secondary);\n white-space: nowrap;\n }\n\n .log-card-body {\n padding: 12px 13px 13px 13px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .log-message {\n font-size: 15px;\n line-height: 1.75;\n color: var(--text-primary);\n word-break: break-word;\n }\n\n .log-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n\n .log-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n font-size: 11px;\n color: var(--text-secondary);\n background: rgba(255,255,255,0.03);\n }\n\n .log-details {\n border-top: 1px solid rgba(255,255,255,0.05);\n padding-top: 10px;\n }\n\n .log-details summary {\n cursor: pointer;\n color: var(--text-secondary);\n font-size: 12px;\n list-style: none;\n }\n\n .log-details summary::-webkit-details-marker {\n display: none;\n }\n\n .log-details pre {\n margin-top: 10px;\n padding: 12px;\n border-radius: 12px;\n border: 1px solid var(--border-color);\n background: rgba(0, 0, 0, 0.22);\n color: var(--text-primary);\n overflow: auto;\n font-size: 13px;\n line-height: 1.6;\n }\n\n @media (max-width: 1360px) {\n .overview-usage-grid {\n grid-template-columns: 1fr;\n }\n\n .context-chip-grid {\n grid-template-columns: 1fr;\n }\n\n .feature-grid {\n grid-template-columns: 1fr;\n }\n\n .hooks-stats {\n grid-template-columns: 1fr;\n }\n }\n\n .right-rail {\n width: 56px;\n border-left: 1px solid var(--border-color);\n background: var(--sidebar-bg);\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 10px 0;\n gap: 8px;\n flex-shrink: 0;\n }\n\n .rail-spacer {\n flex: 1;\n }\n\n .rail-button {\n width: 40px;\n height: 40px;\n border: 1px solid transparent;\n border-radius: 10px;\n background: transparent;\n color: var(--text-secondary);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.2s, color 0.2s, border-color 0.2s;\n }\n\n .rail-button:hover {\n background: var(--hover-bg);\n color: var(--text-primary);\n }\n\n .rail-button.active {\n background: var(--active-bg);\n border-color: var(--border-color);\n color: var(--text-primary);\n }\n\n header {\n background-color: var(--header-bg);\n border-bottom: 1px solid var(--border-color);\n padding: 0 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 56px;\n flex-shrink: 0;\n }\n\n .header-left { display: flex; align-items: center; gap: 12px; }\n\n .toggle-btn {\n background: transparent;\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n padding: 6px;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .toggle-btn:hover { background-color: var(--hover-bg); color: var(--text-primary); }\n\n h1 { font-size: 16px; font-weight: 600; color: var(--text-primary); }\n\n .status-badge {\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 12px;\n background: var(--success-color);\n color: var(--status-text-on-color);\n font-weight: 500;\n }\n .status-badge.disconnected { background: var(--error-color); }\n\n .markdown-body code.inline-code-accent {\n color: var(--code-accent) !important;\n background: transparent !important;\n padding: 0 !important;\n border-radius: 0 !important;\n font-size: inherit !important;\n font-family: inherit !important;\n font-weight: inherit !important;\n line-height: inherit !important;\n }\n\n .markdown-body pre code {\n color: inherit !important;\n }\n\n #chat-container {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n padding-bottom: 200px; /* 增加底部空间,避免输入框遮挡最新消息 */\n display: flex;\n flex-direction: column;\n gap: 24px;\n scroll-behavior: smooth;\n }\n\n .follow-latest-btn {\n position: absolute;\n right: 20px;\n bottom: 132px;\n z-index: 20;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n border: 1px solid var(--border-color);\n background: color-mix(in srgb, var(--panel-bg) 88%, transparent);\n color: var(--text-secondary);\n border-radius: 999px;\n padding: 10px 14px;\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n box-shadow: 0 8px 24px var(--shadow-color);\n backdrop-filter: blur(10px);\n transition: all 0.2s ease;\n }\n\n .follow-latest-btn:hover {\n color: var(--text-primary);\n border-color: var(--text-secondary);\n transform: translateY(-1px);\n }\n\n .follow-latest-btn.active {\n color: var(--text-primary);\n border-color: color-mix(in srgb, var(--success-color) 55%, var(--border-color));\n background: color-mix(in srgb, var(--success-color) 16%, var(--panel-bg));\n }\n\n .follow-latest-btn.hidden {\n opacity: 0;\n pointer-events: none;\n transform: translateY(8px);\n }\n\n .follow-latest-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: var(--text-muted);\n transition: background 0.2s ease, box-shadow 0.2s ease;\n }\n\n .follow-latest-btn.active .follow-latest-dot {\n background: var(--success-color);\n box-shadow: 0 0 0 4px color-mix(in srgb, var(--success-color) 18%, transparent);\n }\n\n @media (max-width: 768px) {\n .follow-latest-btn {\n right: 16px;\n bottom: 116px;\n padding: 9px 12px;\n }\n }\n\n /* Message Styles */\n .message-row {\n display: flex;\n flex-direction: column;\n max-width: 800px;\n width: 100%;\n margin: 0 auto;\n gap: 6px;\n }\n\n .message-meta {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--text-secondary);\n padding: 0 4px;\n }\n\n .role-badge { font-weight: 600; text-transform: uppercase; font-size: 11px; letter-spacing: 0.5px; }\n\n .message-action {\n background: transparent;\n border: 1px solid var(--border-color);\n color: var(--text-secondary);\n border-radius: 999px;\n padding: 2px 10px;\n font-size: 11px;\n cursor: pointer;\n transition: all 0.2s ease;\n font-family: inherit;\n }\n\n .message-action:hover {\n color: var(--text-primary);\n border-color: var(--text-secondary);\n background: var(--hover-bg);\n }\n\n .message-content {\n padding: 12px 16px;\n border-radius: 8px;\n font-size: 15px;\n line-height: 1.6;\n position: relative;\n overflow-wrap: break-word;\n }\n\n .message-content.collapsed {\n max-height: 160px;\n mask-image: linear-gradient(to bottom, black 60%, transparent 100%);\n -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);\n overflow: hidden;\n }\n\n .message-row.system.long-content { align-items: stretch; }\n .message-row.system.long-content .message-content {\n text-align: left !important;\n width: 100%;\n }\n .message-content.collapsed {\n max-height: 160px;\n mask-image: linear-gradient(to bottom, black 60%, transparent 100%);\n -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);\n cursor: default;\n }\n \n .expand-toggle-bar {\n display: flex;\n justify-content: center;\n padding-top: 4px;\n margin-bottom: 8px;\n width: 100%;\n }\n \n .expand-toggle-btn {\n background: var(--tool-msg-bg);\n border: 1px solid var(--border-color);\n color: var(--text-secondary);\n border-radius: 12px;\n padding: 4px 12px;\n font-size: 11px;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n font-family: inherit;\n }\n \n .expand-toggle-btn:hover {\n background: var(--hover-bg);\n color: var(--text-primary);\n border-color: var(--text-secondary);\n }\n \n .expand-toggle-btn svg {\n width: 12px;\n height: 12px;\n fill: currentColor;\n }\n\n .message-row.user .message-content {\n background-color: var(--user-msg-bg);\n color: var(--text-primary);\n align-self: flex-end;\n max-width: 85%;\n border-bottom-right-radius: 2px;\n }\n \n .message-row.user { align-items: flex-end; }\n .message-row.user .message-meta { justify-content: flex-end; }\n\n .message-row.assistant .message-content {\n background-color: transparent;\n padding: 0;\n width: 100%;\n }\n\n .message-row.system { align-items: center; gap: 4px; margin: 12px auto; opacity: 0.8; }\n .message-row.system .message-content {\n background: transparent;\n border: 1px dashed var(--border-color);\n padding: 8px 16px;\n font-size: 13px;\n color: var(--text-secondary);\n text-align: center;\n }\n\n /* Tool Styles */\n .tool-call-container {\n margin-top: 12px;\n border: 1px solid var(--border-color);\n border-radius: 6px;\n overflow: hidden;\n background: var(--tool-msg-bg);\n }\n \n .tool-header {\n background: var(--hover-bg);\n padding: 6px 12px;\n font-size: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--text-secondary);\n border-bottom: 1px solid var(--border-color);\n }\n .tool-header-name { color: var(--text-primary); font-weight: 600; }\n .tool-content { padding: 12px; font-size: 13px; color: var(--text-primary); overflow-x: auto; }\n\n .tool-result-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--hover-bg);\n border-radius: 6px 6px 0 0;\n font-size: 12px;\n color: var(--text-secondary);\n border: 1px solid var(--border-color);\n border-bottom: none;\n }\n\n .status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n .status-dot.success { background-color: var(--success-color); box-shadow: 0 0 4px rgba(25, 135, 84, 0.4); }\n .status-dot.error { background-color: var(--error-color); box-shadow: 0 0 4px rgba(220, 53, 69, 0.4); }\n\n .tool-result-body {\n background: var(--tool-msg-bg);\n border: 1px solid var(--border-color);\n border-top: none;\n border-radius: 0 0 6px 6px;\n padding: 12px;\n overflow-x: auto;\n overflow-y: auto;\n max-height: 400px;\n font-size: 13px;\n }\n\n /* System Tool Rendering */\n .bash-command { font-family: \"Fira Code\", \"Cascadia Code\", \"Source Code Pro\", \"JetBrains Mono\", ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace; color: var(--text-primary); }\n .bash-output { font-family: \"Fira Code\", \"Cascadia Code\", \"Source Code Pro\", \"JetBrains Mono\", ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace; color: var(--text-secondary); white-space: pre-wrap; margin: 0; }\n .file-path { color: #58a6ff; }\n \n .ls-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 12px;\n max-height: 500px;\n overflow-y: auto;\n }\n .ls-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px;\n background: var(--hover-bg);\n border: 1px solid var(--border-color);\n border-radius: 6px;\n cursor: default;\n transition: all 0.2s;\n }\n .ls-item:hover { backgro\"Fira Code\", \"Cascadia Code\", \"Source Code Pro\", \"JetBrains Mono\", und: var(--active-bg); border-color: #444; transform: translateY(-1px); }\n .ls-icon { color: var(--text-secondary); display: flex; align-items: center; }\n .ls-name { font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-primary); }\n \n .markdown-body table {\n width: 100% !important;\n border-collapse: collapse !important;\n margin-bottom: 16px !important;\n background-color: #161b22 !important;\n border-radius: 6px !important;\n overflow: hidden !important;\n display: table !important;\n }\n .markdown-body th, .markdown-body td {\n padding: 8px 12px !important;\n border: 1px solid #30363d !important;\n }\n .markdown-body th {\n background-color: #161b22 !important;\n font-weight: 600 !important;\n text-align: left !important;\n color: var(--text-primary) !important;\n }\n .markdown-body tr { background-color: #0d1117 !important; }\n .markdown-body tr:nth-child(2n) { background-color: #161b22 !important; }\n\n .tool-error {\n background: rgba(220, 53, 69, 0.1);\n border: 1px solid rgba(220, 53, 69, 0.3);\n color: #ff6b6b;\n padding: 10px 14px;\n border-radius: 6px;\n display: flex;\n align-items: flex-start;\n gap: 10px;\n font-size: 13px;\n line-height: 1.5;\n }\n .tool-error svg { flex-shrink: 0; margin-top: 2px; }\n \n .markdown-body { color: var(--text-primary) !important; font-family: inherit !important; background: transparent !important; }\n .markdown-body pre { background-color: #111 !important; border-radius: 6px; }\n\n .empty-state { text-align: center; margin-top: 20vh; color: var(--text-secondary); }\n\n /* 通知状态指示器 */\n .notification-status {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: var(--hover-bg);\n border-radius: 6px;\n font-size: 12px;\n color: var(--text-secondary);\n }\n .notification-status.active {\n color: var(--text-primary);\n background: rgba(88, 166, 255, 0.15);\n }\n .notification-indicator {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--text-secondary);\n animation: pulse 1.5s ease-in-out infinite;\n }\n .notification-status.active .notification-indicator {\n background: #58a6ff;\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n .notification-phase {\n font-weight: 500;\n }\n .notification-char-count {\n font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace;\n }\n \n /* Reasoning */\n .reasoning-block {\n margin-bottom: 16px;\n border-left: 2px solid var(--border-color);\n padding-left: 12px;\n background: rgba(255, 255, 255, 0.02);\n border-radius: 0 4px 4px 0;\n }\n .reasoning-header { \n padding: 6px 0;\n font-size: 12px; color: var(--text-secondary); cursor: pointer; display: flex; align-items: center; gap: 6px;\n user-select: none;\n }\n .reasoning-content { display: none; padding-bottom: 8px; font-size: 13px; color: var(--text-secondary); }\n .reasoning-block.expanded .reasoning-content { display: block; animation: fadeIn 0.2s; }\n .reasoning-icon { transition: transform 0.2s; }\n .reasoning-block.expanded .reasoning-icon { transform: rotate(90deg); }\n \n @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n\n /* ========== Read 工具:简洁代码显示(无框体) ========== */\n .code-read-container {\n font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace;\n font-size: 12px;\n line-height: 20px;\n }\n .code-read-line {\n display: flex;\n white-space: pre;\n }\n .code-read-line-num {\n padding-right: 16px;\n text-align: right;\n color: #6e7681;\n user-select: none;\n min-width: 40px;\n flex-shrink: 0;\n }\n .code-read-content {\n flex: 1;\n white-space: pre;\n }\n\n /* ========== Diff2Html 深色模式适配 ========== */\n .d2h-wrapper {\n font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace;\n font-size: 12px;\n background: transparent !important;\n }\n .d2h-file-header {\n background-color: #21262d !important;\n border-bottom: 1px solid #30363d !important;\n padding: 4px 8px !important;\n }\n .d2h-file-name {\n color: #c9d1d9 !important;\n font-size: 11px !important;\n }\n .d2h-diff-table {\n font-size: 12px;\n }\n .d2h-code-line-ctn {\n color: #c9d1d9;\n }\n .d2h-code-side {\n border: none !important;\n }\n .d2h-file-diff {\n border: none !important;\n border-radius: 0 !important;\n background: transparent !important;\n }\n .d2h-files-diff {\n border: none !important;\n border-radius: 0 !important;\n background: transparent !important;\n }\n\n /* 修复行号不随内容滚动的问题:将 absolute 改为 sticky */\n .d2h-code-side-linenumber {\n position: sticky !important;\n left: 0 !important;\n z-index: 1 !important;\n }\n .d2h-code-linenumber {\n position: sticky !important;\n left: 0 !important;\n z-index: 1 !important;\n }\n\n /* 用户输入容器(默认隐藏) */\n #user-input-container {\n display: none;\n position: absolute;\n bottom: 50px;\n left: 0;\n right: 0;\n z-index: 1000;\n display: flex;\n justify-content: center;\n pointer-events: none; /* 让空白区域不阻挡点击 */\n }\n\n #user-input-container:not(:empty) {\n display: flex;\n }\n\n #user-input-container.choice-input-active {\n top: 56px;\n bottom: 0;\n padding: 24px;\n align-items: center;\n background: rgba(5, 7, 12, 0.64);\n backdrop-filter: blur(5px);\n pointer-events: auto;\n }\n\n body[data-theme=\"light\"] #user-input-container.choice-input-active {\n background: rgba(18, 20, 26, 0.32);\n }\n\n #user-input-container.choice-input-active.choice-collapsed {\n top: auto;\n bottom: 22px;\n padding: 0 24px;\n align-items: flex-end;\n background: transparent;\n backdrop-filter: none;\n pointer-events: none;\n }\n\n .user-input-card {\n pointer-events: auto;\n background: var(--input-card-bg);\n border: 1px solid var(--input-card-border);\n border-radius: 24px;\n padding: 18px 24px;\n box-shadow: 0 8px 32px var(--shadow-strong);\n width: 85%;\n max-width: 800px;\n display: flex;\n flex-direction: column;\n }\n\n .user-input-header {\n display: none;\n }\n\n .user-input-prompt {\n display: none; \n }\n\n .user-input-textarea {\n width: 100%;\n background: transparent;\n color: var(--text-primary);\n border: none;\n padding: 0;\n font-family: inherit; /* 跟随 body 字体,即用户消息的字体 */\n font-size: 16px;\n line-height: 1.6;\n resize: none;\n box-sizing: border-box;\n outline: none;\n min-height: 26px;\n max-height: 300px; \n }\n \n .user-input-textarea::placeholder {\n color: var(--text-muted);\n }\n\n .user-input-textarea:focus {\n border-color: transparent;\n }\n\n .user-input-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 12px;\n gap: 12px;\n }\n\n .user-input-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n }\n\n .user-input-action {\n border: 1px solid var(--border-color);\n background: transparent;\n color: var(--text-secondary);\n border-radius: 999px;\n padding: 6px 12px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n transition: all 0.2s ease;\n }\n\n .user-input-action:hover {\n color: var(--text-primary);\n border-color: var(--text-secondary);\n background: var(--hover-bg);\n }\n\n .user-input-action.danger {\n color: #d9534f;\n border-color: rgba(217, 83, 79, 0.35);\n }\n\n .user-input-action.primary {\n color: var(--text-primary);\n border-color: var(--text-primary);\n }\n\n .user-choice-card {\n gap: 12px;\n padding: 18px 20px;\n border-radius: 20px;\n width: min(100%, 520px);\n max-height: min(100%, 720px);\n overflow: auto;\n box-shadow: 0 28px 70px rgba(0, 0, 0, 0.38);\n }\n\n .user-choice-card:focus {\n outline: none;\n border-color: var(--input-card-border);\n }\n\n .user-choice-topline {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n }\n\n .user-choice-title {\n flex: 1;\n min-width: 0;\n color: var(--text-primary);\n font-size: 18px;\n line-height: 1.45;\n font-weight: 700;\n }\n\n .user-choice-progress {\n margin-left: auto;\n color: var(--text-muted);\n font-size: 12px;\n line-height: 1.4;\n white-space: nowrap;\n padding-top: 1px;\n }\n\n .user-choice-question {\n color: var(--text-secondary);\n font-size: 15px;\n line-height: 1.55;\n }\n\n .user-choice-close {\n width: 30px;\n height: 30px;\n border-radius: 999px;\n border: 1px solid var(--border-color);\n background: transparent;\n color: var(--text-primary);\n cursor: pointer;\n flex-shrink: 0;\n font-size: 18px;\n line-height: 1;\n }\n\n .user-choice-close:hover {\n background: var(--hover-bg);\n }\n\n .user-choice-options {\n display: grid;\n gap: 8px;\n }\n\n .user-choice-option {\n width: 100%;\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr);\n gap: 10px;\n align-items: flex-start;\n border: 1px solid var(--border-color);\n background: transparent;\n color: var(--text-primary);\n border-radius: 12px;\n padding: 10px 12px;\n cursor: pointer;\n font-family: inherit;\n text-align: left;\n transition: border-color 0.16s ease, background 0.16s ease;\n }\n\n .user-choice-option:hover,\n .user-choice-option.active {\n border-color: var(--text-secondary);\n background: var(--hover-bg);\n }\n\n .user-choice-key {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n border: 1px solid var(--border-color);\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n line-height: 1;\n margin-top: 1px;\n }\n\n .user-choice-option.active .user-choice-key {\n color: var(--text-primary);\n border-color: var(--text-primary);\n }\n\n .user-choice-label {\n font-size: 14px;\n line-height: 1.35;\n color: var(--text-primary);\n overflow-wrap: anywhere;\n }\n\n .user-choice-description {\n margin-top: 3px;\n font-size: 12px;\n line-height: 1.45;\n color: var(--text-muted);\n overflow-wrap: anywhere;\n }\n\n .user-choice-custom {\n display: none;\n margin-top: -2px;\n }\n\n .user-choice-custom.active {\n display: block;\n }\n\n .user-choice-custom textarea,\n .user-choice-supplement textarea {\n width: 100%;\n min-height: 42px;\n max-height: 140px;\n resize: none;\n box-sizing: border-box;\n border: 1px solid var(--border-color);\n background: transparent;\n color: var(--text-primary);\n border-radius: 12px;\n padding: 10px 12px;\n font: inherit;\n font-size: 14px;\n line-height: 1.45;\n outline: none;\n }\n\n .user-choice-custom textarea:focus,\n .user-choice-supplement textarea:focus {\n border-color: var(--text-secondary);\n }\n\n .user-choice-supplement {\n display: none;\n margin-top: -2px;\n }\n\n .user-choice-supplement.active {\n display: block;\n }\n\n .user-choice-supplement-label {\n font-size: 12px;\n color: var(--text-muted);\n margin-bottom: 4px;\n }\n\n .user-choice-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n color: var(--text-muted);\n font-size: 12px;\n line-height: 1.4;\n }\n\n .user-choice-submit {\n border: 1px solid var(--text-primary);\n background: var(--text-primary);\n color: var(--bg-primary);\n border-radius: 999px;\n padding: 7px 14px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n\n .user-choice-mini {\n pointer-events: auto;\n width: min(100% - 24px, 420px);\n border: 1px solid var(--input-card-border);\n background: var(--input-card-bg);\n color: var(--text-primary);\n border-radius: 999px;\n padding: 10px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n box-shadow: 0 10px 32px var(--shadow-strong);\n cursor: pointer;\n font-family: inherit;\n text-align: left;\n }\n\n .user-choice-mini-title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n line-height: 1.4;\n }\n\n .user-choice-mini-meta {\n color: var(--text-muted);\n font-size: 12px;\n white-space: nowrap;\n }\n\n </style>\n</head>\n<body>\n <div class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-header\">\n <div class=\"sidebar-title\">Agents</div>\n </div>\n <div class=\"agent-list\" id=\"agent-list\">\n <!-- Agent items -->\n </div>\n </div>\n\n <div class=\"main-content\">\n <header>\n <div class=\"header-left\">\n <button class=\"toggle-btn\" id=\"sidebar-toggle\" title=\"Toggle Sidebar\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"></line>\n <line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"></line>\n <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"></line>\n </svg>\n </button>\n <h1 id=\"current-agent-name\">Agent Debugger</h1>\n <div id=\"notification-status\" class=\"notification-status\" style=\"display: none;\">\n <div class=\"notification-indicator\"></div>\n <span class=\"notification-phase\" id=\"notification-phase\"></span>\n <span class=\"notification-char-count\" id=\"notification-char-count\"></span>\n <span>字符</span>\n </div>\n </div>\n <span id=\"connection-status\" class=\"status-badge\">Connected</span>\n </header>\n\n <div id=\"chat-container\">\n <div class=\"empty-state\">Waiting for messages...</div>\n </div>\n <button id=\"follow-latest-btn\" class=\"follow-latest-btn hidden\" type=\"button\"></button>\n \n <div id=\"user-input-container\"></div>\n </div>\n\n <div class=\"right-workspace\">\n <aside id=\"feature-panel\" class=\"feature-panel\">\n <div id=\"feature-panel-resizer\" class=\"feature-panel-resizer\" title=\"Resize panel\"></div>\n <div class=\"feature-panel-header\">\n <div id=\"feature-panel-title\" class=\"feature-panel-title\">Workspace</div>\n </div>\n <div id=\"feature-panel-body\" class=\"feature-panel-body\">\n <div class=\"feature-panel-empty\">\n <div>选择右侧功能按钮以展开面板。</div>\n </div>\n </div>\n </aside>\n\n <aside class=\"right-rail\" id=\"right-rail\">\n <button class=\"rail-button\" id=\"rail-workspace\" title=\"Structure\" data-panel=\"workspace\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"></rect>\n <path d=\"M9 4v16\"></path>\n </svg>\n </button>\n <button class=\"rail-button\" id=\"rail-monitor\" title=\"Monitor\" data-panel=\"monitor\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n <path d=\"M4 19h16\"></path>\n <path d=\"M7 16V9\"></path>\n <path d=\"M12 16V5\"></path>\n <path d=\"M17 16v-4\"></path>\n </svg>\n </button>\n <button class=\"rail-button\" id=\"rail-hooks\" title=\"Features\" data-panel=\"hooks\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n <path d=\"M8 7a3 3 0 1 0-6 0 3 3 0 0 0 6 0Z\"></path>\n <path d=\"M22 7a3 3 0 1 0-6 0 3 3 0 0 0 6 0Z\"></path>\n <path d=\"M15 17a3 3 0 1 0-6 0 3 3 0 0 0 6 0Z\"></path>\n <path d=\"M8 7h8\"></path>\n <path d=\"M11 10v4\"></path>\n </svg>\n </button>\n <button class=\"rail-button\" id=\"rail-inspector\" title=\"Reverse Hooks\" data-panel=\"inspector\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n <circle cx=\"11\" cy=\"11\" r=\"6\"></circle>\n <path d=\"m20 20-3.5-3.5\"></path>\n </svg>\n </button>\n <button class=\"rail-button\" id=\"rail-logs\" title=\"Logs\" data-panel=\"logs\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n <path d=\"M4 19h16\"></path>\n <path d=\"M7 15h3\"></path>\n <path d=\"M7 11h10\"></path>\n <path d=\"M7 7h7\"></path>\n </svg>\n </button>\n <button class=\"rail-button\" id=\"rail-mcp\" title=\"MCP\" data-panel=\"mcp\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"3\"></rect>\n <path d=\"M7 12h4\"></path>\n <path d=\"M13 12h4\"></path>\n <path d=\"M12 9v6\"></path>\n </svg>\n </button>\n <div class=\"rail-spacer\"></div>\n <button class=\"rail-button\" id=\"language-toggle\" title=\"Switch Language\" type=\"button\">EN</button>\n <button class=\"rail-button\" id=\"theme-toggle\" title=\"切换主题\" type=\"button\">\n <svg id=\"theme-toggle-icon\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n <path d=\"M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z\"></path>\n </svg>\n </button>\n </aside>\n </div>\n\n <div id=\"agent-context-menu\" class=\"context-menu\">\n <button id=\"delete-agent-action\" class=\"context-menu-item danger\" type=\"button\">删除 Agent</button>\n </div>\n\n <script>\n // Feature 模板映射(从 API 动态加载)\n let FEATURE_TEMPLATE_MAP = {};\n\n // 加载 Feature 模板映射\n async function loadFeatureTemplateMap() {\n try {\n const response = await fetch('/api/templates/feature' + (currentAgentId ? '?agentId=' + encodeURIComponent(currentAgentId) : ''));\n if (response.ok) {\n const data = await response.json();\n if (Object.keys(data).length > 0) {\n FEATURE_TEMPLATE_MAP = data;\n return true;\n }\n }\n return false;\n } catch (e) {\n console.warn('[Viewer] Failed to load feature templates:', e);\n return false;\n }\n }\n\n // 重新加载 Feature 模板映射\n async function reloadFeatureTemplateMap() {\n console.log('[Viewer] Reloading feature templates...');\n const success = await loadFeatureTemplateMap();\n if (success) {\n // 重新加载当前页面的工具配置\n if (currentAgentId) {\n await loadAgentTools(currentAgentId);\n // 重新渲染当前消息\n if (currentMessages.length > 0) {\n render(currentMessages);\n }\n }\n }\n }\n\n const container = document.getElementById('chat-container');\n const statusBadge = document.getElementById('connection-status');\n const agentList = document.getElementById('agent-list');\n const currentAgentTitle = document.getElementById('current-agent-name');\n const sidebar = document.getElementById('sidebar');\n const sidebarToggle = document.getElementById('sidebar-toggle');\n const featurePanel = document.getElementById('feature-panel');\n const featurePanelTitle = document.getElementById('feature-panel-title');\n const featurePanelBody = document.getElementById('feature-panel-body');\n const featurePanelResizer = document.getElementById('feature-panel-resizer');\n const agentContextMenu = document.getElementById('agent-context-menu');\n const deleteAgentAction = document.getElementById('delete-agent-action');\n const followLatestButton = document.getElementById('follow-latest-btn');\n const railButtons = Array.from(document.querySelectorAll('.rail-button'));\n const languageToggle = document.getElementById('language-toggle');\n const themeToggle = document.getElementById('theme-toggle');\n\n let currentAgentId = null;\n let allAgents = [];\n let currentMessages = [];\n let currentInputRequests = [];\n let choiceInputState = {};\n let toolRenderConfigs = {};\n let TOOL_NAMES = {};\n let contextMenuAgentId = null;\n let activeFeaturePanel = null;\n let featurePanelWidth = 320;\n let currentTheme = localStorage.getItem('agentdev-theme') || 'dark';\n let currentLanguage = localStorage.getItem('agentdev-language') || 'zh';\n let currentHookInspector = { lifecycleOrder: [], features: [], hooks: [] };\n let currentHookInspectorSignature = '';\n let currentOverviewSnapshot = getEmptyOverviewSnapshot();\n let currentOverviewSignature = '';\n let currentLogs = [];\n let currentLogsSignature = '';\n let currentMcpInfo = null;\n let logPanelScope = 'current';\n let logFilters = {\n search: '',\n level: 'all',\n feature: 'all',\n lifecycle: 'all',\n };\n let selectedOverviewLifecycle = 'StepFinish';\n let selectedFeatureName = null;\n let followLatestEnabled = true;\n let suppressFollowScrollEvent = false;\n let pendingFollowToBottom = false;\n let lastManualScrollIntentAt = 0;\n let followScrollSettleToken = 0;\n\n const I18N = {\n zh: {\n page_title: 'Agent 调试器',\n sidebar_toggle: '切换侧栏',\n resize_panel: '调整面板宽度',\n chars: '字符',\n status_connected: '已连接',\n status_disconnected: '已断开',\n status_no_agent: '无 Agent',\n empty_waiting: '等待消息中...',\n panel_hint: '选择右侧功能按钮以展开面板。',\n panel_structure: '结构',\n panel_monitor: '监视',\n panel_features: '功能特性',\n panel_reverse_hooks: '反向钩子',\n panel_logs: '日志',\n panel_mcp: 'MCP',\n panel_loop_flow: '工作流',\n panel_runtime: '运行概览',\n panel_current_turn: '本轮',\n panel_session_total: '累计',\n panel_context: '上下文',\n panel_features_summary: '功能概览',\n panel_select_lifecycle: '选择一个生命周期阶段',\n panel_inspector: '检查器',\n panel_connection: '连接状态',\n panel_messages: '消息数',\n panel_usage: '用量',\n panel_features_label: '功能特性',\n panel_status_summary: '状态分布',\n panel_enabled: '已启用',\n panel_partial: '部分启用',\n panel_disabled: '已关闭',\n panel_removed: '已移除',\n panel_total: '总数',\n panel_all_features: '全部功能特性',\n panel_registered: '已注册',\n panel_no_features: '没有 Feature',\n panel_no_feature_data: '当前 Agent 尚未上报 feature 信息。',\n panel_feature_details: 'Feature 详情',\n panel_loaded_tools: '已加载工具',\n panel_no_tools: '当前没有已注册工具。',\n panel_close: '关闭',\n panel_no_hook_data: '没有 Hook 数据',\n panel_no_hook_data_desc: '当前 Agent 尚未上报 feature / hook 监视信息。',\n panel_all_lifecycle_slots: '完整 8 个生命周期槽位',\n panel_attached: '已挂载',\n panel_no_handlers: '当前没有挂载任何处理函数。',\n stat_active_agent: '当前 Agent',\n stat_context_length: '上下文长度',\n stat_turn_tokens: '本轮 Tokens',\n stat_total_tokens: '累计 Tokens',\n stat_cache_hit_rate: '缓存命中率',\n stat_turn_requests: '本轮请求数',\n metric_messages: '消息数',\n metric_chars: '字符数',\n metric_turns: '轮次',\n metric_tool_calls: '工具调用',\n metric_input_tokens: '输入',\n metric_output_tokens: '输出',\n metric_requests: 'LLM 请求',\n metric_cache_hit_requests: '命中请求',\n metric_cache_miss_requests: '未命中请求',\n metric_avg_per_request: '每次平均',\n metric_cache_read: '缓存读取',\n metric_cache_write: '缓存写入',\n metric_cache_hit_rate: '命中率',\n metric_input_share: '输入占比',\n metric_output_share: '输出占比',\n metric_latest_turn: '最近一轮',\n metric_session_total: '整个会话',\n metric_no_calls: '还没有 LLM 请求',\n metric_unavailable: '暂无',\n feature_source_missing: '暂无源码信息',\n feature_enabled: 'enabled',\n feature_partial: 'partial',\n feature_disabled: 'disabled',\n feature_removed: 'removed',\n feature_hooks: 'hooks',\n feature_tools: 'tools',\n feature_messages: '条消息',\n feature_registered_label: '已注册',\n feature_active_tools: '启用工具',\n feature_tool_enabled: 'enabled',\n feature_tool_disabled: 'disabled',\n feature_tool_removed: 'removed',\n feature_tool_superseded: '已替代',\n feature_tool_render: 'render',\n feature_open_details: '查看详情',\n feature_status_label: '状态',\n standalone_tools_title: '直接注册的工具',\n standalone_tools_desc: '非 Feature 注册的工具',\n mcp_section_kicker: 'MCP 服务器',\n mcp_hero_title: 'Debugger Hub MCP 服务',\n structure_kicker: 'ReAct 循环拓扑',\n structure_hero_title: 'Feature Hooks 映射',\n structure_subtitle: '查看当前 agent 的 hook 映射、循环阶段说明,以及用于阅读会话链路的开发者视角解释。',\n overview_kicker: '运行监视',\n overview_hero_title: 'LLM 调用监视',\n mcp_item_tool: '工具',\n mcp_item_resource: '资源',\n mcp_item_prompt: '提示模板',\n active_none: '无',\n delete_agent: '删除 Agent',\n delete_confirm: '删除这个已断开的 Agent?这只会从当前调试界面移除它的记录。',\n delete_failed: '删除 Agent 失败: ',\n theme_toggle_light: '切换到浅色模式',\n theme_toggle_dark: '切换到深色模式',\n language_toggle: '切换到英文',\n language_toggle_short: 'EN',\n structure_tooltip: '结构',\n monitor_tooltip: '监视',\n features_tooltip: '功能特性',\n reverse_hooks_tooltip: '反向钩子',\n logs_tooltip: '日志',\n mcp_tooltip: 'MCP',\n mcp_subtitle: '调试器内置的只读 MCP 服务器,可供外部客户端和 agent 自观察使用。',\n mcp_enabled: '已启用',\n mcp_disabled: '已禁用',\n mcp_endpoint: '端点',\n mcp_transport: '传输',\n mcp_tools: '工具',\n mcp_resources: '资源',\n mcp_prompts: '提示模板',\n mcp_client_config: '客户端配置',\n mcp_claude_desktop: 'Claude Desktop 配置',\n mcp_codex: 'Codex 配置',\n mcp_manual: '手动初始化示例',\n mcp_tool_list: '工具一览',\n mcp_resource_list: '资源一览',\n mcp_prompt_list: '提示模板一览',\n mcp_loading: '正在加载 MCP 信息...',\n logs_scope: '范围',\n logs_scope_current: '只看当前 Agent',\n logs_scope_all: '全部',\n logs_search: '搜索',\n logs_search_placeholder: '按消息、namespace、feature、hook 检索',\n logs_level: '级别',\n logs_level_all: '全部级别',\n logs_level_debug: 'Debug 及以上',\n logs_level_info: 'Info 及以上',\n logs_level_warn: 'Warn 及以上',\n logs_level_error: '仅 Error',\n logs_feature: 'Feature',\n logs_feature_all: '全部 Feature',\n logs_lifecycle: 'Lifecycle',\n logs_lifecycle_all: '全部生命周期',\n logs_empty: '当前筛选条件下没有日志。',\n logs_total: '日志',\n logs_details: '查看结构化数据',\n phase_thinking: '思考中',\n phase_content: '生成内容',\n phase_tool_calling: '工具调用',\n input_placeholder: '正在与 Agent 对话',\n follow_latest_on: '跟随最新',\n follow_latest_off: '回到底部',\n expand: '展开',\n collapse: '收起',\n thinking_process: '思考过程',\n hook_kind: 'hook',\n subagent: '子代理',\n subagent_done: '已完成',\n subagent_view_messages: '查看消息 >',\n delete_failed_generic: '删除失败',\n overview_subtitle: '查看上下文、Token 消耗和缓存命中等信息',\n },\n en: {\n page_title: 'Agent Debugger',\n sidebar_toggle: 'Toggle Sidebar',\n resize_panel: 'Resize panel',\n chars: 'chars',\n status_connected: 'Connected',\n status_disconnected: 'Disconnected',\n status_no_agent: 'No agent',\n empty_waiting: 'Waiting for messages...',\n panel_hint: 'Select a tool on the right rail to open the panel.',\n panel_structure: 'Structure',\n panel_monitor: 'Monitor',\n panel_features: 'Features',\n panel_reverse_hooks: 'Reverse Hooks',\n panel_logs: 'Logs',\n panel_mcp: 'MCP',\n panel_loop_flow: 'Workflow',\n panel_runtime: 'Runtime Overview',\n panel_current_turn: 'Current Turn',\n panel_session_total: 'Session Total',\n panel_context: 'Context',\n panel_features_summary: 'Feature Summary',\n panel_select_lifecycle: 'Select a lifecycle stage',\n panel_inspector: 'Inspector',\n panel_connection: 'Connection',\n panel_messages: 'Messages',\n panel_usage: 'Usage',\n panel_features_label: 'Features',\n panel_status_summary: 'Status Mix',\n panel_enabled: 'enabled',\n panel_partial: 'partial',\n panel_disabled: 'disabled',\n panel_removed: 'removed',\n panel_total: 'total',\n panel_all_features: 'All Features',\n panel_registered: 'registered',\n panel_no_features: 'No Features',\n panel_no_feature_data: 'The current agent has not reported feature metadata yet.',\n panel_feature_details: 'Feature Details',\n panel_loaded_tools: 'Loaded Tools',\n panel_no_tools: 'No tools are currently registered for this feature.',\n panel_close: 'Close',\n panel_no_hook_data: 'No Hook Data',\n panel_no_hook_data_desc: 'The current agent has not reported any feature / hook inspector data yet.',\n panel_all_lifecycle_slots: 'All 8 lifecycle slots',\n panel_attached: 'attached',\n panel_no_handlers: 'No attached handlers.',\n stat_active_agent: 'Active Agent',\n stat_context_length: 'Context Length',\n stat_turn_tokens: 'Turn Tokens',\n stat_total_tokens: 'Total Tokens',\n stat_cache_hit_rate: 'Cache Hit Rate',\n stat_turn_requests: 'Turn Requests',\n metric_messages: 'Messages',\n metric_chars: 'Characters',\n metric_turns: 'Turns',\n metric_tool_calls: 'Tool Calls',\n metric_input_tokens: 'Input',\n metric_output_tokens: 'Output',\n metric_requests: 'LLM Requests',\n metric_cache_hit_requests: 'Hit Requests',\n metric_cache_miss_requests: 'Miss Requests',\n metric_avg_per_request: 'Avg / Request',\n metric_cache_read: 'Cache Read',\n metric_cache_write: 'Cache Write',\n metric_cache_hit_rate: 'Hit Rate',\n metric_input_share: 'Input Share',\n metric_output_share: 'Output Share',\n metric_latest_turn: 'Latest Turn',\n metric_session_total: 'Whole Session',\n metric_no_calls: 'No LLM requests yet',\n metric_unavailable: 'N/A',\n feature_source_missing: 'No source metadata',\n feature_enabled: 'enabled',\n feature_partial: 'partial',\n feature_disabled: 'disabled',\n feature_removed: 'removed',\n feature_hooks: 'hooks',\n feature_tools: 'tools',\n feature_messages: 'messages',\n feature_registered_label: 'registered',\n feature_active_tools: 'Active Tools',\n feature_tool_enabled: 'enabled',\n feature_tool_disabled: 'disabled',\n feature_tool_removed: 'removed',\n feature_tool_superseded: 'superseded',\n feature_tool_render: 'render',\n feature_open_details: 'Open details',\n feature_status_label: 'Status',\n standalone_tools_title: 'Direct Registered Tools',\n standalone_tools_desc: 'Tools registered outside of Features',\n mcp_section_kicker: 'Model Context Protocol',\n mcp_hero_title: 'Debugger MCP Server',\n structure_kicker: 'ReAct Loop Topology',\n structure_hero_title: 'Feature Hooks Map',\n structure_subtitle: 'Inspect the current agent hook map, loop timing guide, and developer-facing explanations for reading the session flow.',\n overview_kicker: 'Runtime Monitor',\n overview_hero_title: 'Current turn, totals, and cache at a glance',\n mcp_item_tool: 'tool',\n mcp_item_resource: 'resource',\n mcp_item_prompt: 'prompt',\n active_none: 'None',\n delete_agent: 'Delete Agent',\n delete_confirm: 'Delete this disconnected agent? This only removes it from the current debugger view.',\n delete_failed: 'Failed to delete agent: ',\n theme_toggle_light: 'Switch to light mode',\n theme_toggle_dark: 'Switch to dark mode',\n language_toggle: 'Switch to Chinese',\n language_toggle_short: '中',\n structure_tooltip: 'Structure',\n monitor_tooltip: 'Monitor',\n features_tooltip: 'Features',\n reverse_hooks_tooltip: 'Reverse Hooks',\n logs_tooltip: 'Logs',\n mcp_tooltip: 'MCP',\n mcp_subtitle: 'Built-in read-only MCP server for external clients and agent self-observation.',\n mcp_enabled: 'Enabled',\n mcp_disabled: 'Disabled',\n mcp_endpoint: 'Endpoint',\n mcp_transport: 'Transport',\n mcp_tools: 'Tools',\n mcp_resources: 'Resources',\n mcp_prompts: 'Prompts',\n mcp_client_config: 'Client Config',\n mcp_claude_desktop: 'Claude Desktop config',\n mcp_codex: 'Codex config',\n mcp_manual: 'Manual initialize example',\n mcp_tool_list: 'Tool Catalog',\n mcp_resource_list: 'Resource Catalog',\n mcp_prompt_list: 'Prompt Catalog',\n mcp_loading: 'Loading MCP info...',\n logs_scope: 'Scope',\n logs_scope_current: 'Current agent',\n logs_scope_all: 'All agents',\n logs_search: 'Search',\n logs_search_placeholder: 'Search message, namespace, feature, hook',\n logs_level: 'Level',\n logs_level_all: 'All levels',\n logs_level_debug: 'Debug and up',\n logs_level_info: 'Info and up',\n logs_level_warn: 'Warn and up',\n logs_level_error: 'Error only',\n logs_feature: 'Feature',\n logs_feature_all: 'All features',\n logs_lifecycle: 'Lifecycle',\n logs_lifecycle_all: 'All lifecycles',\n logs_empty: 'No logs match the current filters.',\n logs_total: 'logs',\n logs_details: 'Structured payload',\n phase_thinking: 'Thinking',\n phase_content: 'Streaming',\n phase_tool_calling: 'Tool Calling',\n input_placeholder: 'Chatting with the agent',\n follow_latest_on: 'Following Latest',\n follow_latest_off: 'Jump to Latest',\n expand: 'Expand',\n collapse: 'Collapse',\n thinking_process: 'Thinking Process',\n hook_kind: 'hook',\n subagent: 'SubAgent',\n subagent_done: 'Completed',\n subagent_view_messages: 'View messages >',\n delete_failed_generic: 'Delete failed',\n overview_subtitle: 'Separate current context, current-turn usage, session totals, and request-level cache hits so each metric means exactly one thing.',\n },\n };\n\n function t(key) {\n const table = I18N[currentLanguage] || I18N.zh;\n return table[key] || key;\n }\n\n function getFeatureStatus(feature) {\n return feature && feature.status ? feature.status : (feature && feature.enabled ? 'enabled' : 'partial');\n }\n\n function getFeatureStatusLabel(status) {\n if (status === 'removed') return t('feature_removed');\n if (status === 'disabled') return t('feature_disabled');\n if (status === 'partial') return t('feature_partial');\n return t('feature_enabled');\n }\n\n function getStatusBadgeClass(status) {\n return 'feature-badge status-' + escapeHtml(status || 'enabled');\n }\n\n function getEmptyStateHtml() {\n return '<div class=\"empty-state\">' + escapeHtml(t('empty_waiting')) + '</div>';\n }\n\n function getFeaturePanelEmptyHtml() {\n return '<div class=\"feature-panel-empty\"><div>' + escapeHtml(t('panel_hint')) + '</div></div>';\n }\n\n function getToggleButtonLabel(collapsed) {\n return collapsed\n ? '<svg viewBox=\"0 0 24 24\"><path d=\"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\"/></svg> ' + escapeHtml(t('expand'))\n : '<svg viewBox=\"0 0 24 24\"><path d=\"M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\"/></svg> ' + escapeHtml(t('collapse'));\n }\n\n function isNearBottom() {\n const threshold = 48;\n return container.scrollHeight - container.scrollTop - container.clientHeight <= threshold;\n }\n\n function updateFollowLatestButton() {\n if (!followLatestButton) return;\n const hasMessages = currentMessages.length > 0;\n followLatestButton.classList.toggle('hidden', !hasMessages);\n followLatestButton.classList.toggle('active', followLatestEnabled);\n followLatestButton.innerHTML =\n '<span class=\"follow-latest-dot\"></span><span>' +\n escapeHtml(t(followLatestEnabled ? 'follow_latest_on' : 'follow_latest_off')) +\n '</span>';\n }\n\n function markManualScrollIntent() {\n lastManualScrollIntentAt = Date.now();\n }\n\n function hasRecentManualScrollIntent() {\n return Date.now() - lastManualScrollIntentAt < 500;\n }\n\n function animateScrollTo(targetTop, duration = 150) {\n const settleToken = ++followScrollSettleToken;\n lastManualScrollIntentAt = 0;\n suppressFollowScrollEvent = true;\n\n const startTop = container.scrollTop;\n const delta = targetTop - startTop;\n if (Math.abs(delta) < 1 || duration <= 0) {\n container.scrollTop = targetTop;\n suppressFollowScrollEvent = false;\n return;\n }\n\n const startAt = performance.now();\n const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);\n\n const step = (now) => {\n if (settleToken !== followScrollSettleToken) {\n return;\n }\n\n const progress = Math.min(1, (now - startAt) / duration);\n container.scrollTop = startTop + delta * easeOutCubic(progress);\n\n if (progress < 1) {\n requestAnimationFrame(step);\n return;\n }\n\n container.scrollTop = targetTop;\n suppressFollowScrollEvent = false;\n };\n\n requestAnimationFrame(step);\n }\n\n function scrollToLatest(behavior = 'smooth') {\n const targetTop = container.scrollHeight;\n if (behavior === 'auto') {\n followScrollSettleToken += 1;\n lastManualScrollIntentAt = 0;\n suppressFollowScrollEvent = true;\n container.scrollTop = targetTop;\n suppressFollowScrollEvent = false;\n return;\n }\n\n animateScrollTo(targetTop, 70);\n }\n\n function setFollowLatest(enabled, options = {}) {\n const { scroll = false, behavior = 'smooth' } = options;\n followLatestEnabled = enabled;\n if (enabled) {\n lastManualScrollIntentAt = 0;\n }\n updateFollowLatestButton();\n if (enabled && scroll) {\n scrollToLatest(behavior);\n }\n }\n\n function scheduleScrollToLatest(behavior = 'smooth') {\n pendingFollowToBottom = true;\n requestAnimationFrame(() => {\n if (!pendingFollowToBottom) return;\n pendingFollowToBottom = false;\n scrollToLatest(behavior);\n });\n }\n\n function shortenSourcePath(value) {\n if (!value) return '';\n const normalized = String(value).replace(/\\\\\\\\/g, '/');\n const srcIndex = normalized.lastIndexOf('/src/');\n if (srcIndex >= 0) return normalized.slice(srcIndex + 1);\n const agentdevIndex = normalized.lastIndexOf('/AgentDev/');\n if (agentdevIndex >= 0) return normalized.slice(agentdevIndex + 10);\n return normalized;\n }\n\n const FULL_HOOK_LIFECYCLE_ORDER = [\n 'AgentInitiate',\n 'AgentDestroy',\n 'CallStart',\n 'CallFinish',\n 'StepStart',\n 'StepFinish',\n 'ToolUse',\n 'ToolFinished',\n ];\n\n function getHookInspectorSignature(snapshot) {\n return JSON.stringify(snapshot || { lifecycleOrder: [], features: [], hooks: [] });\n }\n\n function getEmptyOverviewSnapshot() {\n return {\n updatedAt: 0,\n context: {\n messageCount: 0,\n charCount: 0,\n toolCallCount: 0,\n turnCount: 0,\n },\n usageStats: {\n totalUsage: {\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n },\n calls: [],\n totalRequests: 0,\n totalCacheHitRequests: 0,\n },\n };\n }\n\n function normalizeOverviewSnapshot(snapshot) {\n const empty = getEmptyOverviewSnapshot();\n if (!snapshot || typeof snapshot !== 'object') {\n return empty;\n }\n\n return {\n updatedAt: typeof snapshot.updatedAt === 'number' ? snapshot.updatedAt : 0,\n context: {\n messageCount: typeof snapshot.context?.messageCount === 'number' ? snapshot.context.messageCount : 0,\n charCount: typeof snapshot.context?.charCount === 'number' ? snapshot.context.charCount : 0,\n toolCallCount: typeof snapshot.context?.toolCallCount === 'number' ? snapshot.context.toolCallCount : 0,\n turnCount: typeof snapshot.context?.turnCount === 'number' ? snapshot.context.turnCount : 0,\n },\n usageStats: {\n totalUsage: {\n inputTokens: typeof snapshot.usageStats?.totalUsage?.inputTokens === 'number' ? snapshot.usageStats.totalUsage.inputTokens : 0,\n outputTokens: typeof snapshot.usageStats?.totalUsage?.outputTokens === 'number' ? snapshot.usageStats.totalUsage.outputTokens : 0,\n totalTokens: typeof snapshot.usageStats?.totalUsage?.totalTokens === 'number' ? snapshot.usageStats.totalUsage.totalTokens : 0,\n cacheCreationTokens: typeof snapshot.usageStats?.totalUsage?.cacheCreationTokens === 'number' ? snapshot.usageStats.totalUsage.cacheCreationTokens : 0,\n cacheReadTokens: typeof snapshot.usageStats?.totalUsage?.cacheReadTokens === 'number' ? snapshot.usageStats.totalUsage.cacheReadTokens : 0,\n reasoningTokens: typeof snapshot.usageStats?.totalUsage?.reasoningTokens === 'number' ? snapshot.usageStats.totalUsage.reasoningTokens : 0,\n audioTokens: typeof snapshot.usageStats?.totalUsage?.audioTokens === 'number' ? snapshot.usageStats.totalUsage.audioTokens : 0,\n },\n calls: Array.isArray(snapshot.usageStats?.calls) ? snapshot.usageStats.calls.map((call) => ({\n ...call,\n cacheHitRequests: typeof call?.cacheHitRequests === 'number' ? call.cacheHitRequests : 0,\n })) : [],\n totalRequests: typeof snapshot.usageStats?.totalRequests === 'number' ? snapshot.usageStats.totalRequests : 0,\n totalCacheHitRequests: typeof snapshot.usageStats?.totalCacheHitRequests === 'number' ? snapshot.usageStats.totalCacheHitRequests : 0,\n },\n };\n }\n\n function getOverviewSignature(snapshot) {\n return JSON.stringify(normalizeOverviewSnapshot(snapshot));\n }\n\n function normalizeHookInspector(snapshot) {\n const raw = snapshot || { lifecycleOrder: [], features: [], hooks: [] };\n const hookMap = new Map((raw.hooks || []).map(group => [group.lifecycle, group]));\n return {\n lifecycleOrder: FULL_HOOK_LIFECYCLE_ORDER.slice(),\n features: (raw.features || []).map(feature => ({\n ...feature,\n tools: feature.tools || [],\n })),\n hooks: FULL_HOOK_LIFECYCLE_ORDER.map((lifecycle) => {\n const existing = hookMap.get(lifecycle);\n if (existing) return existing;\n return {\n lifecycle,\n kind: lifecycle === 'StepFinish' || lifecycle === 'ToolUse' ? 'decision' : 'notify',\n entries: [],\n };\n }),\n standaloneTools: raw.standaloneTools || undefined,\n };\n }\n\n function setCurrentHookInspector(snapshot) {\n const normalized = normalizeHookInspector(snapshot);\n currentHookInspector = normalized;\n currentHookInspectorSignature = getHookInspectorSignature(normalized);\n if (selectedFeatureName && !normalized.features.some(feature => feature.name === selectedFeatureName)) {\n selectedFeatureName = null;\n }\n }\n\n function setCurrentOverviewSnapshot(snapshot) {\n const normalized = normalizeOverviewSnapshot(snapshot);\n currentOverviewSnapshot = normalized;\n currentOverviewSignature = getOverviewSignature(normalized);\n }\n\n function setCurrentLogs(logs) {\n currentLogs = Array.isArray(logs) ? logs : [];\n currentLogsSignature = JSON.stringify({\n count: currentLogs.length,\n last: currentLogs.length > 0 ? currentLogs[currentLogs.length - 1].id : null,\n });\n }\n\n function formatMetricNumber(value) {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return '0';\n }\n return value.toLocaleString();\n }\n\n function formatRate(numerator, denominator) {\n if (!denominator) {\n return '0%';\n }\n return Math.round((numerator / denominator) * 100) + '%';\n }\n\n function getLatestCallSummary(overview) {\n const calls = Array.isArray(overview?.usageStats?.calls) ? overview.usageStats.calls : [];\n if (calls.length === 0) return null;\n return calls.slice().sort((a, b) => (a.callIndex || 0) - (b.callIndex || 0))[calls.length - 1];\n }\n\n function getUsageBreakdown(summary, fallbackRequests = 0) {\n const totalUsage = summary?.totalUsage || {};\n const totalTokens = totalUsage.totalTokens || 0;\n const inputTokens = totalUsage.inputTokens || 0;\n const outputTokens = totalUsage.outputTokens || 0;\n const requests = typeof summary?.stepCount === 'number'\n ? summary.stepCount\n : fallbackRequests;\n const cacheHitRequests = typeof summary?.cacheHitRequests === 'number'\n ? summary.cacheHitRequests\n : 0;\n\n return {\n inputTokens,\n outputTokens,\n totalTokens,\n requests,\n cacheHitRequests,\n cacheMissRequests: Math.max(0, requests - cacheHitRequests),\n cacheHitRate: formatRate(cacheHitRequests, requests),\n avgPerRequest: requests > 0 ? Math.round(totalTokens / requests) : 0,\n cacheReadTokens: totalUsage.cacheReadTokens || 0,\n cacheCreationTokens: totalUsage.cacheCreationTokens || 0,\n inputShare: totalTokens > 0 ? Math.round((inputTokens / totalTokens) * 100) : 0,\n outputShare: totalTokens > 0 ? Math.round((outputTokens / totalTokens) * 100) : 0,\n };\n }\n\n function renderTokenBar(inputTokens, outputTokens) {\n const total = inputTokens + outputTokens;\n const inputWidth = total > 0 ? (inputTokens / total) * 100 : 50;\n const outputWidth = total > 0 ? (outputTokens / total) * 100 : 50;\n return [\n '<div class=\"usage-bar\">',\n '<div class=\"usage-bar-fill input\" style=\"width:' + inputWidth + '%\"></div>',\n '<div class=\"usage-bar-fill output\" style=\"width:' + outputWidth + '%\"></div>',\n '</div>',\n ].join('');\n }\n\n function renderRateRing(percent, label, meta) {\n const safePercent = Math.max(0, Math.min(100, percent));\n return [\n '<div class=\"rate-ring-card\">',\n '<div class=\"rate-ring\" style=\"--ring-percent:' + safePercent + ';\">',\n '<div class=\"rate-ring-inner\">',\n '<div class=\"rate-ring-value\">' + safePercent + '%</div>',\n '<div class=\"rate-ring-label\">' + escapeHtml(label) + '</div>',\n '</div>',\n '</div>',\n '<div class=\"rate-ring-meta\">' + escapeHtml(meta) + '</div>',\n '</div>',\n ].join('');\n }\n\n function renderUsageCard(title, summaryLabel, breakdown) {\n return [\n '<div class=\"usage-card\">',\n '<div class=\"usage-card-header\">',\n '<div>',\n '<div class=\"usage-card-title\">' + escapeHtml(title) + '</div>',\n '<div class=\"usage-card-subtitle\">' + escapeHtml(summaryLabel) + '</div>',\n '</div>',\n '<div class=\"usage-card-total\">' + formatMetricNumber(breakdown.totalTokens) + '</div>',\n '</div>',\n renderTokenBar(breakdown.inputTokens, breakdown.outputTokens),\n '<div class=\"usage-split-legend\">',\n '<span><i class=\"legend-dot input\"></i>' + escapeHtml(t('metric_input_tokens')) + ' ' + formatMetricNumber(breakdown.inputTokens) + '</span>',\n '<span><i class=\"legend-dot output\"></i>' + escapeHtml(t('metric_output_tokens')) + ' ' + formatMetricNumber(breakdown.outputTokens) + '</span>',\n '</div>',\n '<div class=\"usage-stat-grid\">',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_requests')) + '</div><div class=\"usage-stat-cell-value\">' + formatMetricNumber(breakdown.requests) + '</div></div>',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_avg_per_request')) + '</div><div class=\"usage-stat-cell-value\">' + formatMetricNumber(breakdown.avgPerRequest) + '</div></div>',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_input_share')) + '</div><div class=\"usage-stat-cell-value\">' + breakdown.inputShare + '%</div></div>',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_output_share')) + '</div><div class=\"usage-stat-cell-value\">' + breakdown.outputShare + '%</div></div>',\n '</div>',\n '</div>',\n ].join('');\n }\n\n function renderCacheCard(title, breakdown) {\n const percent = breakdown.requests > 0\n ? Math.round((breakdown.cacheHitRequests / breakdown.requests) * 100)\n : 0;\n return [\n '<div class=\"usage-card cache-card\">',\n '<div class=\"usage-card-header\">',\n '<div class=\"usage-card-title\">' + escapeHtml(title) + '</div>',\n '<div class=\"usage-card-subtitle\">' + escapeHtml(t('metric_cache_hit_rate')) + '</div>',\n '</div>',\n renderRateRing(percent, t('metric_cache_hit_rate'), breakdown.cacheHitRequests + ' / ' + breakdown.requests),\n '<div class=\"usage-stat-grid\">',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_cache_hit_requests')) + '</div><div class=\"usage-stat-cell-value\">' + formatMetricNumber(breakdown.cacheHitRequests) + '</div></div>',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_cache_miss_requests')) + '</div><div class=\"usage-stat-cell-value\">' + formatMetricNumber(breakdown.cacheMissRequests) + '</div></div>',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_cache_read')) + '</div><div class=\"usage-stat-cell-value\">' + formatMetricNumber(breakdown.cacheReadTokens) + '</div></div>',\n '<div class=\"usage-stat-cell\"><div class=\"usage-stat-cell-label\">' + escapeHtml(t('metric_cache_write')) + '</div><div class=\"usage-stat-cell-value\">' + formatMetricNumber(breakdown.cacheCreationTokens) + '</div></div>',\n '</div>',\n '</div>',\n ].join('');\n }\n\n function renderContextChip(label, value, meta) {\n return [\n '<div class=\"context-chip\">',\n '<div class=\"context-chip-label\">' + escapeHtml(label) + '</div>',\n '<div class=\"context-chip-value\">' + escapeHtml(value) + '</div>',\n '<div class=\"context-chip-meta\">' + escapeHtml(meta) + '</div>',\n '</div>',\n ].join('');\n }\n\n function setCurrentMcpInfo(info) {\n currentMcpInfo = info || null;\n }\n\n function getLevelWeight(level) {\n const weights = { trace: 10, debug: 20, info: 30, warn: 40, error: 50 };\n return weights[level] || 0;\n }\n\n function formatLogTimestamp(timestamp) {\n const date = new Date(timestamp);\n if (Number.isNaN(date.getTime())) return '';\n return date.toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n fractionalSecondDigits: 3,\n });\n }\n\n function safePrettyJson(value) {\n try {\n return JSON.stringify(value, null, 2);\n } catch (e) {\n return String(value);\n }\n }\n\n function getFilteredLogs() {\n const search = logFilters.search.trim().toLowerCase();\n const minLevel = logFilters.level;\n return currentLogs.filter((entry) => {\n if (minLevel !== 'all' && getLevelWeight(entry.level) < getLevelWeight(minLevel)) {\n return false;\n }\n if (logFilters.feature !== 'all' && (entry.context?.feature || 'none') !== logFilters.feature) {\n return false;\n }\n if (logFilters.lifecycle !== 'all' && (entry.context?.lifecycle || 'none') !== logFilters.lifecycle) {\n return false;\n }\n if (search) {\n const haystack = [\n entry.message,\n entry.namespace,\n entry.context?.feature,\n entry.context?.lifecycle,\n entry.context?.hookMethod,\n entry.context?.toolName,\n entry.context?.agentName,\n ]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n if (!haystack.includes(search)) {\n return false;\n }\n }\n return true;\n });\n }\n\n function renderLogsPanel() {\n const filteredLogs = getFilteredLogs().slice().reverse();\n const featureOptions = Array.from(new Set(currentLogs.map((entry) => entry.context?.feature).filter(Boolean))).sort();\n const lifecycleOptions = Array.from(new Set(currentLogs.map((entry) => entry.context?.lifecycle).filter(Boolean))).sort();\n\n const toolbar = [\n '<section class=\"log-toolbar\">',\n '<div class=\"log-filter-row\">',\n '<div class=\"log-filter-label\">' + escapeHtml(t('logs_scope')) + '</div>',\n '<div class=\"log-chip-group\">',\n '<button type=\"button\" class=\"log-chip' + (logPanelScope === 'current' ? ' active' : '') + '\" onclick=\"window.setLogPanelScope(&quot;current&quot;)\">' + escapeHtml(t('logs_scope_current')) + '</button>',\n '<button type=\"button\" class=\"log-chip' + (logPanelScope === 'all' ? ' active' : '') + '\" onclick=\"window.setLogPanelScope(&quot;all&quot;)\">' + escapeHtml(t('logs_scope_all')) + '</button>',\n '</div>',\n '</div>',\n '<div class=\"log-filter-row\">',\n '<div class=\"log-filter-label\">' + escapeHtml(t('logs_search')) + '</div>',\n '<input class=\"log-input\" type=\"text\" value=\"' + escapeHtml(logFilters.search) + '\" placeholder=\"' + escapeHtml(t('logs_search_placeholder')) + '\" oninput=\"window.updateLogFilter(&quot;search&quot;, this.value)\">',\n '</div>',\n '<div class=\"log-filter-row\">',\n '<div class=\"log-filter-label\">' + escapeHtml(t('logs_level')) + '</div>',\n '<select class=\"log-select\" onchange=\"window.updateLogFilter(&quot;level&quot;, this.value)\">',\n '<option value=\"all\"' + (logFilters.level === 'all' ? ' selected' : '') + '>' + escapeHtml(t('logs_level_all')) + '</option>',\n '<option value=\"debug\"' + (logFilters.level === 'debug' ? ' selected' : '') + '>' + escapeHtml(t('logs_level_debug')) + '</option>',\n '<option value=\"info\"' + (logFilters.level === 'info' ? ' selected' : '') + '>' + escapeHtml(t('logs_level_info')) + '</option>',\n '<option value=\"warn\"' + (logFilters.level === 'warn' ? ' selected' : '') + '>' + escapeHtml(t('logs_level_warn')) + '</option>',\n '<option value=\"error\"' + (logFilters.level === 'error' ? ' selected' : '') + '>' + escapeHtml(t('logs_level_error')) + '</option>',\n '</select>',\n '<select class=\"log-select\" onchange=\"window.updateLogFilter(&quot;feature&quot;, this.value)\">',\n '<option value=\"all\"' + (logFilters.feature === 'all' ? ' selected' : '') + '>' + escapeHtml(t('logs_feature_all')) + '</option>',\n featureOptions.map((feature) => '<option value=\"' + escapeHtml(feature) + '\"' + (logFilters.feature === feature ? ' selected' : '') + '>' + escapeHtml(feature) + '</option>').join(''),\n '</select>',\n '<select class=\"log-select\" onchange=\"window.updateLogFilter(&quot;lifecycle&quot;, this.value)\">',\n '<option value=\"all\"' + (logFilters.lifecycle === 'all' ? ' selected' : '') + '>' + escapeHtml(t('logs_lifecycle_all')) + '</option>',\n lifecycleOptions.map((lifecycle) => '<option value=\"' + escapeHtml(lifecycle) + '\"' + (logFilters.lifecycle === lifecycle ? ' selected' : '') + '>' + escapeHtml(lifecycle) + '</option>').join(''),\n '</select>',\n '</div>',\n '<div class=\"log-summary\"><span>' + String(filteredLogs.length) + ' ' + escapeHtml(t('logs_total')) + '</span><span>' + escapeHtml(logPanelScope === 'current' ? (allAgents.find((agent) => agent.id === currentAgentId)?.name || t('active_none')) : t('logs_scope_all')) + '</span></div>',\n '</section>',\n ].join('');\n\n if (filteredLogs.length === 0) {\n return '<div class=\"log-panel\">' + toolbar + '<div class=\"feature-panel-empty\"><div>' + escapeHtml(t('logs_empty')) + '</div></div></div>';\n }\n\n const rows = filteredLogs.map((entry) => {\n const metaPills = [\n entry.context?.agentName ? '<span class=\"log-pill\">' + escapeHtml(entry.context.agentName) + '</span>' : '',\n entry.context?.feature ? '<span class=\"log-pill\">feature:' + escapeHtml(entry.context.feature) + '</span>' : '',\n entry.context?.lifecycle ? '<span class=\"log-pill\">hook:' + escapeHtml(entry.context.lifecycle) + '</span>' : '',\n entry.context?.hookMethod ? '<span class=\"log-pill\">' + escapeHtml(entry.context.hookMethod) + '()</span>' : '',\n entry.context?.toolName ? '<span class=\"log-pill\">tool:' + escapeHtml(entry.context.toolName) + '</span>' : '',\n typeof entry.context?.step === 'number' ? '<span class=\"log-pill\">step ' + String(entry.context.step) + '</span>' : '',\n typeof entry.context?.callIndex === 'number' ? '<span class=\"log-pill\">call ' + String(entry.context.callIndex) + '</span>' : '',\n ].filter(Boolean).join('');\n\n const detailBlock = entry.data !== undefined\n ? '<details class=\"log-details\"><summary>' + escapeHtml(t('logs_details')) + '</summary><pre>' + escapeHtml(safePrettyJson(entry.data)) + '</pre></details>'\n : '';\n\n return [\n '<article class=\"log-card\">',\n '<div class=\"log-card-head\">',\n '<div class=\"log-card-main\">',\n '<span class=\"log-level ' + escapeHtml(entry.level) + '\">' + escapeHtml(entry.level) + '</span>',\n '<span class=\"log-namespace\">' + escapeHtml(entry.namespace) + '</span>',\n '</div>',\n '<div class=\"log-timestamp\">' + escapeHtml(formatLogTimestamp(entry.timestamp)) + '</div>',\n '</div>',\n '<div class=\"log-card-body\">',\n '<div class=\"log-message\">' + escapeHtml(entry.message) + '</div>',\n metaPills ? '<div class=\"log-meta\">' + metaPills + '</div>' : '',\n detailBlock,\n '</div>',\n '</article>',\n ].join('');\n }).join('');\n\n return '<div class=\"log-panel\">' + toolbar + '<section class=\"log-list\">' + rows + '</section></div>';\n }\n\n function renderMcpItems(items, typeLabel) {\n if (!Array.isArray(items) || items.length === 0) {\n return '<div class=\"feature-panel-empty\"><div>' + escapeHtml(t('active_none')) + '</div></div>';\n }\n\n return '<div class=\"mcp-list\">' + items.map((item) => {\n const name = item.name || item.uri || '';\n return [\n '<article class=\"mcp-item\">',\n '<div class=\"mcp-item-head\">',\n '<div class=\"mcp-item-name\">' + escapeHtml(name) + '</div>',\n '<div class=\"mcp-item-type\">' + escapeHtml(typeLabel) + '</div>',\n '</div>',\n '<div class=\"mcp-item-desc\">' + escapeHtml(item.description || '') + '</div>',\n '</article>',\n ].join('');\n }).join('') + '</div>';\n }\n\n function renderMcpPanel() {\n if (!currentMcpInfo) {\n return '<div class=\"feature-panel-empty\"><div>' + escapeHtml(t('mcp_loading')) + '</div></div>';\n }\n\n const info = currentMcpInfo;\n return [\n '<div class=\"mcp-panel\">',\n '<section class=\"mcp-hero\">',\n '<div class=\"hooks-kicker\">' + escapeHtml(t('mcp_section_kicker')) + '</div>',\n '<div class=\"hooks-hero-title\">' + escapeHtml(t('mcp_hero_title')) + '</div>',\n '<div class=\"hooks-hero-subtitle\">' + escapeHtml(t('mcp_subtitle')) + '</div>',\n '<div class=\"mcp-status-pill\">' + escapeHtml(info.enabled ? t('mcp_enabled') : t('mcp_disabled')) + '</div>',\n '</section>',\n '<section class=\"feature-panel-section\">',\n '<div class=\"feature-panel-section-title\">' + escapeHtml(t('panel_inspector')) + '</div>',\n '<div class=\"mcp-grid\">',\n '<div class=\"mcp-stat\"><div class=\"mcp-stat-label\">' + escapeHtml(t('mcp_endpoint')) + '</div><div class=\"mcp-stat-value\">' + escapeHtml(info.endpoint || '') + '</div></div>',\n '<div class=\"mcp-stat\"><div class=\"mcp-stat-label\">' + escapeHtml(t('mcp_transport')) + '</div><div class=\"mcp-stat-value\">' + escapeHtml(info.transport || '') + '</div></div>',\n '<div class=\"mcp-stat\"><div class=\"mcp-stat-label\">' + escapeHtml(t('mcp_tools')) + '</div><div class=\"mcp-stat-value\">' + String((info.tools || []).length) + '</div></div>',\n '<div class=\"mcp-stat\"><div class=\"mcp-stat-label\">' + escapeHtml(t('mcp_resources')) + '</div><div class=\"mcp-stat-value\">' + String((info.resources || []).length) + '</div></div>',\n '</div>',\n '</section>',\n '<section class=\"feature-panel-section\">',\n '<div class=\"feature-panel-section-title\">' + escapeHtml(t('mcp_client_config')) + '</div>',\n '<div class=\"mcp-item-desc\" style=\"margin-bottom:8px;\">' + escapeHtml(t('mcp_claude_desktop')) + '</div>',\n '<pre class=\"mcp-code\">' + escapeHtml(safePrettyJson(info.commands?.claudeDesktop?.json || {})) + '</pre>',\n '<div class=\"mcp-item-desc\" style=\"margin:12px 0 8px 0;\">' + escapeHtml(t('mcp_codex')) + '</div>',\n '<pre class=\"mcp-code\">' + escapeHtml(safePrettyJson(info.commands?.codex?.json || {})) + '</pre>',\n '<div class=\"mcp-item-desc\" style=\"margin:12px 0 8px 0;\">' + escapeHtml(t('mcp_manual')) + '</div>',\n '<pre class=\"mcp-code\">' + escapeHtml(info.commands?.curlInitialize || '') + '</pre>',\n '</section>',\n '<section class=\"feature-panel-section\">',\n '<div class=\"feature-panel-section-title\">' + escapeHtml(t('mcp_tool_list')) + '</div>',\n renderMcpItems(info.tools || [], t('mcp_item_tool')),\n '</section>',\n '<section class=\"feature-panel-section\">',\n '<div class=\"feature-panel-section-title\">' + escapeHtml(t('mcp_resource_list')) + '</div>',\n renderMcpItems(info.resources || [], t('mcp_item_resource')),\n '</section>',\n '<section class=\"feature-panel-section\">',\n '<div class=\"feature-panel-section-title\">' + escapeHtml(t('mcp_prompt_list')) + '</div>',\n renderMcpItems(info.prompts || [], t('mcp_item_prompt')),\n '</section>',\n '</div>',\n ].join('');\n }\n\n const lifecycleDocs = {\n AgentInitiate: {\n title: { zh: 'Agent 初始化阶段', en: 'Agent initialization phase' },\n body: {\n zh: [\n '这个时机只会在 agent 第一次真正进入工作状态时触发一次,适合做长生命周期资源的准备工作,比如启动后台服务、建立连接、预热缓存,或者把框架级能力挂进运行环境。',\n '',\n '~~~ts',\n '@AgentInitiate',\n 'async boot(ctx) {',\n ' await this.indexWorkspace();',\n ' await this.startObserver();',\n '}',\n '~~~',\n '',\n '如果某个 feature 要在整个会话期间维持状态,这里通常是它最稳妥的切入点。相比 CallStart,它不会被每次用户输入重复触发。',\n ].join('\\\\n'),\n en: [\n 'This moment fires only once when the agent truly enters its working state. It is the right place for long-lived setup such as booting background services, opening connections, warming caches, or mounting framework-level helpers.',\n '',\n '~~~ts',\n '@AgentInitiate',\n 'async boot(ctx) {',\n ' await this.indexWorkspace();',\n ' await this.startObserver();',\n '}',\n '~~~',\n '',\n 'If a feature needs to hold state across the whole session, this is usually the safest insertion point. Unlike CallStart, it is not repeated on every user request.',\n ].join('\\\\n'),\n },\n },\n AgentDestroy: {\n title: { zh: 'Agent 销毁阶段', en: 'Agent destroy phase' },\n body: { zh: [\n '这是 agent 生命周期的收尾点,用来释放外部资源、停止后台线程、断开连接,以及把调试信息或缓存安全落盘。',\n '',\n '~~~ts',\n '@AgentDestroy',\n 'async cleanup() {',\n ' await this.workerPool.stop();',\n ' await this.cache.flush();',\n '}',\n '~~~',\n '',\n '如果一个 feature 在 AgentInitiate 做了重量级初始化,就应该在这里成对地清理掉。',\n ].join('\\\\n'),\n en: [\n 'This is the closing stage of the agent lifecycle. Use it to release external resources, stop workers, close connections, and flush traces or caches safely to disk.',\n '',\n '~~~ts',\n '@AgentDestroy',\n 'async cleanup() {',\n ' await this.workerPool.stop();',\n ' await this.cache.flush();',\n '}',\n '~~~',\n '',\n 'If a feature performs heavyweight setup in AgentInitiate, it should usually tear that work down here.',\n ].join('\\\\n') },\n },\n CallStart: {\n title: { zh: 'Call 开始前', en: 'Before call start' },\n body: { zh: [\n '这个时机发生在系统提示词之后、用户输入正式写入上下文之前。它非常适合做输入重写、前置注入和会话级别的轻量整理。',\n '',\n '~~~ts',\n '@CallStart',\n 'async rewriteInput(ctx) {',\n ' const raw = ctx.agent?.getUserInput() ?? ctx.input;',\n ' ctx.agent?.setUserInput(raw.trim());',\n '}',\n '~~~',\n '',\n '如果你想观察 feature 如何“提前影响”一次调用,这里通常是最有解释力的节点。',\n ].join('\\\\n'),\n en: [\n 'This timing happens after the system prompt is ready but before the user input is committed into context. It is ideal for input rewriting, pre-injection, and lightweight call-level normalization.',\n '',\n '~~~ts',\n '@CallStart',\n 'async rewriteInput(ctx) {',\n ' const raw = ctx.agent?.getUserInput() ?? ctx.input;',\n ' ctx.agent?.setUserInput(raw.trim());',\n '}',\n '~~~',\n '',\n 'If you want to explain how a feature affects a call before the model sees it, this is usually the clearest node.',\n ].join('\\\\n') },\n },\n CallFinish: {\n title: { zh: 'Call 结束后', en: 'After call finish' },\n body: { zh: [\n '这是一次完整调用结束后的结算点。适合做摘要、记录、指标更新、落日志,而不适合决定下一轮 ReAct 要不要继续。',\n '',\n '~~~ts',\n '@CallFinish',\n 'async afterCall(ctx) {',\n ' this.metrics.track(ctx.completed, ctx.steps);',\n '}',\n '~~~',\n '',\n '它更像“回合总结”,而不是流程控制点。',\n ].join('\\\\n'),\n en: [\n 'This is the settlement point after a full call completes. It fits summarization, logging, and metrics updates, but it is not the place to decide whether the next ReAct turn should continue.',\n '',\n '~~~ts',\n '@CallFinish',\n 'async afterCall(ctx) {',\n ' this.metrics.track(ctx.completed, ctx.steps);',\n '}',\n '~~~',\n '',\n 'It behaves more like an end-of-call summary than a flow-control decision point.',\n ].join('\\\\n') },\n },\n StepStart: {\n title: { zh: 'Step 开始前', en: 'Before step start' },\n body: { zh: [\n '每轮 ReAct 循环刚开始时都会进入这里。适合做上下文补丁、提醒注入、局部状态同步。这类钩子往往会高频出现。',\n '',\n '~~~ts',\n '@StepStart',\n 'async injectReminder(ctx) {',\n ' if (this.shouldRemind()) {',\n ' ctx.context.add({ role: \"system\", content: this.reminder });',\n ' }',\n '}',\n '~~~',\n '',\n '因为它会在每一轮执行,所以调试器里把它单独看出来很重要,否则很难解释某些系统消息为什么总会出现。',\n ].join('\\\\n'),\n en: [\n 'Every ReAct iteration enters here right at the beginning. It is useful for context patching, reminder injection, and local state synchronization. These hooks often run at high frequency.',\n '',\n '~~~ts',\n '@StepStart',\n 'async injectReminder(ctx) {',\n ' if (this.shouldRemind()) {',\n ' ctx.context.add({ role: \"system\", content: this.reminder });',\n ' }',\n '}',\n '~~~',\n '',\n 'Because it runs every round, surfacing it clearly in the debugger is important; otherwise it is hard to explain why some system messages keep appearing.',\n ].join('\\\\n') },\n },\n StepFinish: {\n title: { zh: 'Step 结束决策点', en: 'Step finish decision point' },\n body: { zh: [\n '这是 ReAct 循环里最关键的控制点之一。模型和工具都跑完后,feature 可以在这里决定“继续下一轮”还是“就地结束”。',\n '',\n '~~~ts',\n '@StepFinish',\n 'async decide(ctx) {',\n ' if (this.hasPendingDelegates()) {',\n ' return Decision.Approve;',\n ' }',\n ' return Decision.Continue;',\n '}',\n '~~~',\n '',\n '如果某个 feature 能把 agent 的循环强行维持住,通常就是在这里介入。它解释的是“为什么这轮已经看起来结束了,但系统还在继续跑”。',\n ].join('\\\\n'),\n en: [\n 'This is one of the most important control points in the ReAct loop. After the model and tools finish, a feature can decide whether the loop should continue or end right away.',\n '',\n '~~~ts',\n '@StepFinish',\n 'async decide(ctx) {',\n ' if (this.hasPendingDelegates()) {',\n ' return Decision.Approve;',\n ' }',\n ' return Decision.Continue;',\n '}',\n '~~~',\n '',\n 'If a feature can keep the agent alive beyond what looks like a natural stopping point, it is usually intervening here.',\n ].join('\\\\n') },\n },\n ToolUse: {\n title: { zh: '工具执行前决策点', en: 'Before tool execution decision point' },\n body: { zh: [\n '这是另一个高价值观察位点。工具真正执行前,feature 可以在这里批准、拒绝或者放行。所有安全策略、危险操作拦截都很适合在这里实现。',\n '',\n '~~~ts',\n '@ToolUse',\n 'async guard(ctx) {',\n ' if (ctx.call.name === \"run_shell_command\") {',\n ' return Decision.Deny;',\n ' }',\n ' return Decision.Continue;',\n '}',\n '~~~',\n '',\n '调试器里只要看清楚这里挂了谁,很多“为什么工具没执行”或者“为什么执行路径被改写”就能直接定位。',\n ].join('\\\\n'),\n en: [\n 'This is another high-value inspection point. Before a tool actually runs, a feature can approve, deny, or pass it through. Security policy and dangerous-operation guards fit naturally here.',\n '',\n '~~~ts',\n '@ToolUse',\n 'async guard(ctx) {',\n ' if (ctx.call.name === \"run_shell_command\") {',\n ' return Decision.Deny;',\n ' }',\n ' return Decision.Continue;',\n '}',\n '~~~',\n '',\n 'As soon as you can see who is attached here, many \"why did the tool not run?\" questions become much easier to answer.',\n ].join('\\\\n') },\n },\n ToolFinished: {\n title: { zh: '工具执行后通知点', en: 'After tool finished notify point' },\n body: { zh: [\n '工具已经返回结果以后,这里会收到纯通知。适合做后处理、索引、同步外部状态、记录审计信息,但不会改变刚刚那次工具调用本身的结果。',\n '',\n '~~~ts',\n '@ToolFinished',\n 'async record(ctx) {',\n ' this.auditTrail.push({',\n ' tool: ctx.toolName,',\n ' duration: ctx.duration,',\n ' });',\n '}',\n '~~~',\n '',\n '这类钩子更偏“旁路观察”和“后续整理”,所以通常适合完整展开给开发者查链路。',\n ].join('\\\\n'),\n en: [\n 'Once a tool returns its result, this point receives a pure notification. It suits post-processing, indexing, external state sync, and audit recording, but it does not change the result of the tool call that already happened.',\n '',\n '~~~ts',\n '@ToolFinished',\n 'async record(ctx) {',\n ' this.auditTrail.push({',\n ' tool: ctx.toolName,',\n ' duration: ctx.duration,',\n ' });',\n '}',\n '~~~',\n '',\n 'These hooks are more about side-channel observation and cleanup, so they are usually worth showing in full detail to developers.',\n ].join('\\\\n') },\n },\n };\n\n function selectOverviewLifecycle(lifecycle) {\n selectedOverviewLifecycle = lifecycle;\n if (activeFeaturePanel === 'workspace') {\n renderFeaturePanel();\n }\n }\n\n window.selectOverviewLifecycle = selectOverviewLifecycle;\n\n function openFeatureDetails(featureName) {\n selectedFeatureName = featureName;\n if (activeFeaturePanel === 'hooks') {\n renderFeaturePanel();\n }\n }\n\n function closeFeatureDetails() {\n selectedFeatureName = null;\n if (activeFeaturePanel === 'hooks') {\n renderFeaturePanel();\n }\n }\n\n window.openFeatureDetails = openFeatureDetails;\n window.closeFeatureDetails = closeFeatureDetails;\n\n function renderStructurePanel() {\n const activeAgent = allAgents.find(agent => agent.id === currentAgentId);\n const connected = activeAgent ? (activeAgent.connected !== false ? t('status_connected') : t('status_disconnected')) : t('status_no_agent');\n const totalHooks = currentHookInspector.hooks.reduce((sum, group) => sum + group.entries.length, 0);\n const decisionHooks = currentHookInspector.hooks.reduce(\n (sum, group) => sum + group.entries.filter(entry => entry.kind === 'decision').length,\n 0\n );\n const featureStatusCounts = currentHookInspector.features.reduce((acc, feature) => {\n const status = getFeatureStatus(feature);\n acc[status] = (acc[status] || 0) + 1;\n return acc;\n }, { enabled: 0, partial: 0, disabled: 0, removed: 0 });\n const selectedDoc = lifecycleDocs[selectedOverviewLifecycle] || lifecycleDocs.StepFinish;\n const flowChips = currentHookInspector.lifecycleOrder\n .map(name => '<button class=\"hooks-chip' + (name === selectedOverviewLifecycle ? ' active' : '') + '\" type=\"button\" onclick=\"window.selectOverviewLifecycle(&quot;' + escapeHtml(name) + '&quot;)\"><strong>' + escapeHtml(name) + '</strong></button>')\n .join('');\n return [\n '<div class=\"hooks-panel\">',\n '<section class=\"hooks-hero\">',\n '<div class=\"hooks-kicker\">' + escapeHtml(t('structure_kicker')) + '</div>',\n '<div class=\"hooks-hero-title\">' + escapeHtml(t('structure_hero_title')) + '</div>',\n '<div class=\"hooks-hero-subtitle\">' + escapeHtml(t('structure_subtitle')) + '</div>',\n '<div class=\"hooks-stats\">',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">' + escapeHtml(t('stat_active_agent')) + '</div><div class=\"hooks-stat-value\">' + escapeHtml(activeAgent ? activeAgent.name : t('active_none')) + '</div></div>',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">Hooks</div><div class=\"hooks-stat-value\">' + String(totalHooks) + '</div></div>',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">Decision</div><div class=\"hooks-stat-value\">' + String(decisionHooks) + '</div></div>',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">' + escapeHtml(t('panel_features_label')) + '</div><div class=\"hooks-stat-value\">' + String(currentHookInspector.features.length) + '</div></div>',\n '</div>',\n '</section>',\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(t('panel_inspector')) + '</div><div class=\"hooks-section-meta\">' + escapeHtml(connected) + '</div></div>',\n '<div class=\"feature-grid\">',\n '<div class=\"feature-card\"><div class=\"feature-card-name\">' + escapeHtml(t('panel_connection')) + '</div><div class=\"feature-card-detail\"><span>' + escapeHtml(connected) + '</span><span>' + String(currentMessages.length) + ' ' + escapeHtml(t('feature_messages')) + '</span></div></div>',\n '<div class=\"feature-card\"><div class=\"feature-card-name\">' + escapeHtml(t('panel_features_label')) + '</div><div class=\"feature-card-detail\"><span>' + String(currentHookInspector.features.length) + ' ' + escapeHtml(t('panel_total')) + '</span><span>' + String(featureStatusCounts.enabled) + ' ' + escapeHtml(t('panel_enabled')) + '</span><span>' + String(featureStatusCounts.partial) + ' ' + escapeHtml(t('panel_partial')) + '</span><span>' + String(featureStatusCounts.disabled) + ' ' + escapeHtml(t('panel_disabled')) + '</span><span>' + String(featureStatusCounts.removed) + ' ' + escapeHtml(t('panel_removed')) + '</span></div></div>',\n '</div>',\n '</section>',\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(t('panel_loop_flow')) + '</div><div class=\"hooks-section-meta\">' + escapeHtml(t('panel_select_lifecycle')) + '</div></div>',\n '<div class=\"hooks-strip\">' + flowChips + '</div>',\n '</section>',\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(selectedOverviewLifecycle) + '</div><div class=\"hooks-section-meta\">' + escapeHtml(selectedDoc.title[currentLanguage] || selectedDoc.title.zh) + '</div></div>',\n '<div class=\"feature-panel-section overview-doc\"><div class=\"markdown-body\">' + marked.parse(selectedDoc.body[currentLanguage] || selectedDoc.body.zh) + '</div></div>',\n '</section>',\n '</div>',\n ].join('');\n }\n\n function renderMonitorPanel() {\n const activeAgent = allAgents.find(agent => agent.id === currentAgentId);\n const connected = activeAgent ? (activeAgent.connected !== false ? t('status_connected') : t('status_disconnected')) : t('status_no_agent');\n const overview = currentOverviewSnapshot || getEmptyOverviewSnapshot();\n const totalUsage = overview.usageStats?.totalUsage || {};\n const latestCall = getLatestCallSummary(overview);\n const currentBreakdown = getUsageBreakdown(latestCall, 0);\n const totalBreakdown = getUsageBreakdown({\n totalUsage,\n stepCount: overview.usageStats.totalRequests || 0,\n cacheHitRequests: overview.usageStats.totalCacheHitRequests || 0,\n }, overview.usageStats.totalRequests || 0);\n const contextLengthLabel = formatMetricNumber(overview.context.charCount) + ' chars';\n const latestTurnLabel = latestCall ? formatMetricNumber(currentBreakdown.totalTokens) : t('metric_no_calls');\n return [\n '<div class=\"hooks-panel\">',\n '<section class=\"hooks-hero\">',\n '<div class=\"hooks-kicker\">' + escapeHtml(t('overview_kicker')) + '</div>',\n '<div class=\"hooks-hero-title\">' + escapeHtml(t('overview_hero_title')) + '</div>',\n '<div class=\"hooks-hero-subtitle\">' + escapeHtml(t('overview_subtitle')) + '</div>',\n '<div class=\"hooks-stats\">',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">' + escapeHtml(t('stat_active_agent')) + '</div><div class=\"hooks-stat-value\">' + escapeHtml(activeAgent ? activeAgent.name : t('active_none')) + '</div></div>',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">' + escapeHtml(t('stat_context_length')) + '</div><div class=\"hooks-stat-value\">' + escapeHtml(contextLengthLabel) + '</div></div>',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">' + escapeHtml(t('stat_turn_tokens')) + '</div><div class=\"hooks-stat-value\">' + escapeHtml(latestTurnLabel) + '</div></div>',\n '<div class=\"hooks-stat\"><div class=\"hooks-stat-label\">' + escapeHtml(t('stat_cache_hit_rate')) + '</div><div class=\"hooks-stat-value\">' + escapeHtml(totalBreakdown.cacheHitRate) + '</div></div>',\n '</div>',\n '</section>',\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(t('panel_runtime')) + '</div><div class=\"hooks-section-meta\">' + escapeHtml(connected) + '</div></div>',\n '<div class=\"overview-usage-grid\">',\n renderUsageCard(t('panel_current_turn'), latestCall ? t('metric_latest_turn') : t('metric_no_calls'), currentBreakdown),\n renderCacheCard(t('panel_current_turn'), currentBreakdown),\n renderUsageCard(t('panel_session_total'), t('metric_session_total'), totalBreakdown),\n renderCacheCard(t('panel_session_total'), totalBreakdown),\n '</div>',\n '</section>',\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(t('panel_context')) + '</div><div class=\"hooks-section-meta\">' + escapeHtml(t('panel_connection')) + ': ' + escapeHtml(connected) + '</div></div>',\n '<div class=\"context-chip-grid\">',\n renderContextChip(t('metric_messages'), formatMetricNumber(overview.context.messageCount), t('panel_context')),\n renderContextChip(t('metric_chars'), formatMetricNumber(overview.context.charCount), t('stat_context_length')),\n renderContextChip(t('metric_turns'), formatMetricNumber(overview.context.turnCount), t('metric_session_total')),\n renderContextChip(t('metric_tool_calls'), formatMetricNumber(overview.context.toolCallCount), t('metric_latest_turn')),\n '</div>',\n '</section>',\n '</div>',\n ].join('');\n }\n\n function renderFeaturesPanel() {\n if (currentHookInspector.features.length === 0) {\n return '<div class=\"feature-panel-empty\"><div class=\"feature-panel-section\"><div class=\"feature-panel-section-title\">' + escapeHtml(t('panel_no_features')) + '</div><div>' + escapeHtml(t('panel_no_feature_data')) + '</div></div></div>';\n }\n\n const selectedFeature = currentHookInspector.features.find(feature => feature.name === selectedFeatureName) || null;\n const featureCards = currentHookInspector.features\n .map(feature => {\n const status = getFeatureStatus(feature);\n return [\n '<div class=\"feature-card\" role=\"button\" tabindex=\"0\" onclick=\"window.openFeatureDetails(&quot;' + escapeHtml(feature.name) + '&quot;)\" title=\"' + escapeHtml(t('feature_open_details')) + '\">',\n '<div class=\"feature-card-top\">',\n '<div class=\"feature-card-main\">',\n '<span class=\"feature-card-dot\"></span>',\n '<div style=\"min-width:0;\">',\n '<div class=\"feature-card-name\">' + escapeHtml(feature.name) + '</div>',\n '<div class=\"feature-card-file\">' + escapeHtml(shortenSourcePath(feature.source) || t('feature_source_missing')) + '</div>',\n '</div>',\n '</div>',\n '<div class=\"' + getStatusBadgeClass(status) + '\">' + escapeHtml(getFeatureStatusLabel(status)) + '</div>',\n '</div>',\n '<div class=\"feature-card-detail\">',\n '<span>' + String(feature.hookCount) + ' ' + escapeHtml(t('feature_hooks')) + '</span>',\n '<span>' + String(feature.enabledToolCount) + '/' + String(feature.toolCount) + ' ' + escapeHtml(t('feature_tools')) + '</span>',\n feature.description ? '<span>' + escapeHtml(feature.description) + '</span>' : '',\n '</div>',\n '</div>',\n ].join('');\n })\n .join('');\n\n const detailOverlay = selectedFeature ? [\n '<div class=\"feature-detail-overlay\" onclick=\"if (event.target === this) window.closeFeatureDetails()\">',\n '<div class=\"feature-detail-window\">',\n '<div class=\"feature-detail-head\">',\n '<div>',\n '<div class=\"feature-detail-title\">' + escapeHtml(selectedFeature.name) + '</div>',\n '<div class=\"feature-detail-subtitle\">' + escapeHtml(selectedFeature.description || '') + '</div>',\n '</div>',\n '<button class=\"feature-detail-close\" type=\"button\" title=\"' + escapeHtml(t('panel_close')) + '\" onclick=\"window.closeFeatureDetails()\">×</button>',\n '</div>',\n '<div class=\"feature-detail-stats\">',\n '<div class=\"feature-detail-stat\"><div class=\"feature-detail-stat-label\">' + escapeHtml(t('feature_hooks')) + '</div><div class=\"feature-detail-stat-value\">' + String(selectedFeature.hookCount) + '</div></div>',\n '<div class=\"feature-detail-stat\"><div class=\"feature-detail-stat-label\">' + escapeHtml(t('feature_active_tools')) + '</div><div class=\"feature-detail-stat-value\">' + String(selectedFeature.enabledToolCount) + '/' + String(selectedFeature.toolCount) + '</div></div>',\n '<div class=\"feature-detail-stat\"><div class=\"feature-detail-stat-label\">' + escapeHtml(t('feature_status_label')) + '</div><div class=\"feature-detail-stat-value\">' + escapeHtml(getFeatureStatusLabel(getFeatureStatus(selectedFeature))) + '</div></div>',\n '</div>',\n '<div class=\"feature-panel-section\">',\n '<div class=\"feature-panel-section-title\">' + escapeHtml(t('panel_feature_details')) + '</div>',\n '<div class=\"feature-detail-subtitle\">' + escapeHtml(shortenSourcePath(selectedFeature.source) || t('feature_source_missing')) + '</div>',\n '</div>',\n '<div class=\"feature-panel-section\">',\n '<div class=\"feature-panel-section-title\">' + escapeHtml(t('panel_loaded_tools')) + '</div>',\n selectedFeature.tools && selectedFeature.tools.length > 0\n ? '<div class=\"feature-tool-list\">' + selectedFeature.tools.map(tool => [\n '<div class=\"feature-tool-card\">',\n '<div class=\"feature-tool-top\">',\n '<div class=\"feature-tool-name\">' + escapeHtml(tool.name) + '</div>',\n '<div class=\"' + getStatusBadgeClass(tool.state || (tool.enabled ? 'enabled' : 'disabled')) + '\">' + escapeHtml(tool.state === 'superseded' ? t('feature_tool_superseded') : tool.state === 'removed' ? t('feature_tool_removed') : tool.state === 'disabled' || tool.enabled === false ? t('feature_tool_disabled') : t('feature_tool_enabled')) + '</div>',\n '</div>',\n '<div class=\"feature-tool-desc\">' + escapeHtml(tool.description || '') + '</div>',\n '<div class=\"feature-tool-meta\">',\n tool.renderCall ? '<span class=\"feature-tool-pill\">' + escapeHtml(t('feature_tool_render')) + ': call/' + escapeHtml(tool.renderCall) + '</span>' : '',\n tool.renderResult ? '<span class=\"feature-tool-pill\">' + escapeHtml(t('feature_tool_render')) + ': result/' + escapeHtml(tool.renderResult) + '</span>' : '',\n '</div>',\n '</div>',\n ].join('')).join('') + '</div>'\n : '<div class=\"feature-detail-subtitle\">' + escapeHtml(t('panel_no_tools')) + '</div>',\n '</div>',\n '</div>',\n '</div>',\n ].join('') : '';\n\n const standaloneSection = (currentHookInspector.standaloneTools && currentHookInspector.standaloneTools.length > 0)\n ? [\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(t('standalone_tools_title')) + '</div><div class=\"hooks-section-meta\">' + String(currentHookInspector.standaloneTools.length) + '</div></div>',\n '<div class=\"feature-tool-list\">' + currentHookInspector.standaloneTools.map(tool => [\n '<div class=\"feature-tool-card\">',\n '<div class=\"feature-tool-top\">',\n '<div class=\"feature-tool-name\">' + escapeHtml(tool.name) + '</div>',\n '<div class=\"' + getStatusBadgeClass(tool.state || 'enabled') + '\">' + escapeHtml(tool.state === 'superseded' ? t('feature_tool_superseded') : tool.state === 'removed' ? t('feature_tool_removed') : tool.state === 'disabled' ? t('feature_tool_disabled') : t('feature_tool_enabled')) + '</div>',\n '</div>',\n '<div class=\"feature-tool-desc\">' + escapeHtml(tool.description || '') + '</div>',\n tool.source ? '<div class=\"feature-tool-meta\"><span class=\"feature-tool-pill\">source: ' + escapeHtml(tool.source) + '</span></div>' : '',\n '</div>',\n ].join('')).join('') + '</div>',\n '</section>',\n ].join('')\n : '';\n\n return [\n '<div class=\"hooks-panel feature-detail-shell\">',\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(t('panel_all_features')) + '</div><div class=\"hooks-section-meta\">' + String(currentHookInspector.features.length) + ' ' + escapeHtml(t('panel_registered')) + '</div></div>',\n '<div class=\"feature-grid\">' + featureCards + '</div>',\n '</section>',\n standaloneSection,\n detailOverlay,\n '</div>',\n ].join('');\n }\n\n function renderReverseHooksPanel() {\n const hookIcons = {\n AgentInitiate: 'A',\n AgentDestroy: 'D',\n CallStart: 'C',\n CallFinish: 'C',\n StepStart: 'S',\n StepFinish: 'R',\n ToolUse: 'T',\n ToolFinished: 'F',\n };\n\n const lifecycleCards = currentHookInspector.hooks\n .map(group => {\n const entriesHtml = group.entries.map((entry, index) => [\n '<div class=\"hook-step\">',\n '<div class=\"hook-step-order\">' + String(index + 1) + '</div>',\n '<div class=\"hook-step-card\">',\n '<div class=\"hook-step-row\">',\n '<div class=\"hook-step-feature\">' + escapeHtml(entry.featureName) + '</div>',\n '<div class=\"hook-step-kind\">' + escapeHtml(entry.kind) + '</div>',\n '</div>',\n '<div class=\"hook-step-method\">' + escapeHtml(entry.methodName) + '()</div>',\n entry.source && entry.source.display ? '<div class=\"hook-step-location\">' + escapeHtml(shortenSourcePath(entry.source.display)) + '</div>' : '',\n entry.description ? '<div class=\"hook-step-notes\">' + escapeHtml(entry.description) + '</div>' : '',\n '</div>',\n '</div>',\n ].join('')).join('');\n\n return [\n '<section class=\"hook-lifecycle-card\">',\n '<div class=\"hook-lifecycle-head\">',\n '<div class=\"hook-lifecycle-name\">',\n '<span class=\"hook-lifecycle-icon\">' + escapeHtml(hookIcons[group.lifecycle] || 'H') + '</span>',\n '<div>',\n '<div>' + escapeHtml(group.lifecycle) + '</div>',\n '<div class=\"hook-lifecycle-type\">' + escapeHtml(group.kind) + ' ' + escapeHtml(t('hook_kind')) + '</div>',\n '</div>',\n '</div>',\n '<div style=\"display:flex;align-items:center;gap:12px;\">',\n '<div class=\"hooks-section-meta\">' + String(group.entries.length) + ' ' + escapeHtml(t('panel_attached')) + '</div>',\n '</div>',\n '</div>',\n '<div class=\"hook-call-chain\">',\n entriesHtml || '<div class=\"hooks-section-meta\">' + escapeHtml(t('panel_no_handlers')) + '</div>',\n '</div>',\n '</section>',\n ].join('');\n })\n .join('');\n\n if (currentHookInspector.hooks.length === 0) {\n return '<div class=\"feature-panel-empty\"><div class=\"feature-panel-section\"><div class=\"feature-panel-section-title\">' + escapeHtml(t('panel_no_hook_data')) + '</div><div>' + escapeHtml(t('panel_no_hook_data_desc')) + '</div></div></div>';\n }\n\n return [\n '<div class=\"hooks-panel\">',\n '<section class=\"hooks-section\">',\n '<div class=\"hooks-section-header\"><div class=\"hooks-section-title\">' + escapeHtml(t('panel_reverse_hooks')) + '</div><div class=\"hooks-section-meta\">' + escapeHtml(t('panel_all_lifecycle_slots')) + '</div></div>',\n '<div class=\"hook-lifecycle-list\">' + lifecycleCards + '</div>',\n '</section>',\n '</div>',\n ].join('');\n }\n\n const featurePanels = {\n workspace: {\n title: () => t('panel_structure'),\n render: () => renderStructurePanel(),\n },\n monitor: {\n title: () => t('panel_monitor'),\n render: () => renderMonitorPanel(),\n },\n hooks: {\n title: () => t('panel_features'),\n render: () => renderFeaturesPanel(),\n },\n inspector: {\n title: () => t('panel_reverse_hooks'),\n render: () => renderReverseHooksPanel(),\n },\n logs: {\n title: () => t('panel_logs'),\n render: () => renderLogsPanel(),\n },\n mcp: {\n title: () => t('panel_mcp'),\n render: () => renderMcpPanel(),\n },\n };\n\n // Sidebar Toggle\n sidebarToggle.addEventListener('click', () => {\n sidebar.classList.toggle('collapsed');\n });\n\n const renderer = new marked.Renderer();\n renderer.codespan = function(code) {\n const text = typeof code === 'string'\n ? code\n : (code && typeof code === 'object' && 'text' in code\n ? code.text\n : String(code ?? ''));\n return '<code class=\"inline-code-accent\">' + escapeHtml(text) + '</code>';\n };\n\n marked.setOptions({\n renderer,\n highlight: function(code, lang) {\n if (lang && hljs.getLanguage(lang)) {\n return hljs.highlight(code, { language: lang }).value;\n }\n return hljs.highlightAuto(code).value;\n },\n breaks: true\n });\n\n function escapeHtml(text) {\n const str = String(text);\n const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' };\n return str.replace(/[&<>\"']/g, m => map[m]);\n }\n\n // 默认 fallback 模板(当动态加载失败时使用)\n const RENDER_TEMPLATES = {\n 'json': {\n call: (args) => \\`<pre style=\"margin:0; font-size:12px;\">\\${escapeHtml(JSON.stringify(args, null, 2))}</pre>\\`,\n result: (data, success) => {\n if (!success) return formatError(data);\n const displayData = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;\n return \\`<pre class=\"bash-output\">\\${escapeHtml(displayData)}</pre>\\`;\n }\n }\n };\n\n // 模板缓存\n const templateCache = new Map();\n\n function setConnectionStatus(connected) {\n statusBadge.textContent = connected ? t('status_connected') : t('status_disconnected');\n statusBadge.classList.toggle('disconnected', !connected);\n }\n\n function renderThemeToggle() {\n const isLight = currentTheme === 'light';\n themeToggle.title = isLight ? t('theme_toggle_dark') : t('theme_toggle_light');\n themeToggle.innerHTML = isLight\n ? '<svg id=\"theme-toggle-icon\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"12\" cy=\"12\" r=\"4\"></circle><path d=\"M12 2v2.2M12 19.8V22M4.93 4.93l1.56 1.56M17.51 17.51l1.56 1.56M2 12h2.2M19.8 12H22M4.93 19.07l1.56-1.56M17.51 6.49l1.56-1.56\"></path></svg>'\n : '<svg id=\"theme-toggle-icon\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z\"></path></svg>';\n }\n\n function applyLanguage() {\n localStorage.setItem('agentdev-language', currentLanguage);\n document.title = t('page_title');\n\n const sidebarToggleEl = document.getElementById('sidebar-toggle');\n const panelResizerEl = document.getElementById('feature-panel-resizer');\n const notificationCharLabel = document.querySelector('.notification-char-count')?.nextElementSibling;\n const workspaceButton = document.getElementById('rail-workspace');\n const monitorButton = document.getElementById('rail-monitor');\n const hooksButton = document.getElementById('rail-hooks');\n const inspectorButton = document.getElementById('rail-inspector');\n const logsButton = document.getElementById('rail-logs');\n const mcpButton = document.getElementById('rail-mcp');\n\n if (sidebarToggleEl) sidebarToggleEl.title = t('sidebar_toggle');\n if (panelResizerEl) panelResizerEl.title = t('resize_panel');\n if (notificationCharLabel) notificationCharLabel.textContent = t('chars');\n if (workspaceButton) workspaceButton.title = t('structure_tooltip');\n if (monitorButton) monitorButton.title = t('monitor_tooltip');\n if (hooksButton) hooksButton.title = t('features_tooltip');\n if (inspectorButton) inspectorButton.title = t('reverse_hooks_tooltip');\n if (logsButton) logsButton.title = t('logs_tooltip');\n if (mcpButton) mcpButton.title = t('mcp_tooltip');\n\n languageToggle.title = t('language_toggle');\n languageToggle.textContent = t('language_toggle_short');\n deleteAgentAction.textContent = t('delete_agent');\n\n renderThemeToggle();\n renderAgentList();\n renderFeaturePanel();\n\n if (!currentAgentId) {\n currentAgentTitle.textContent = t('page_title');\n statusBadge.textContent = t('status_no_agent');\n }\n\n if (currentMessages.length === 0) {\n container.innerHTML = getEmptyStateHtml();\n updateFollowLatestButton();\n } else {\n render(currentMessages);\n }\n }\n\n function applyTheme(theme) {\n currentTheme = theme === 'light' ? 'light' : 'dark';\n document.body.dataset.theme = currentTheme;\n localStorage.setItem('agentdev-theme', currentTheme);\n renderThemeToggle();\n }\n\n function renderFeaturePanel() {\n const activeElement = document.activeElement;\n const preserveLogSearchFocus = activeFeaturePanel === 'logs' && activeElement && activeElement.classList && activeElement.classList.contains('log-input');\n const preservedSelectionStart = preserveLogSearchFocus && typeof activeElement.selectionStart === 'number'\n ? activeElement.selectionStart\n : null;\n const preservedSelectionEnd = preserveLogSearchFocus && typeof activeElement.selectionEnd === 'number'\n ? activeElement.selectionEnd\n : null;\n\n if (!activeFeaturePanel || !featurePanels[activeFeaturePanel]) {\n featurePanel.classList.remove('open');\n featurePanelTitle.textContent = t('panel_structure');\n featurePanelBody.innerHTML = getFeaturePanelEmptyHtml();\n railButtons.forEach(button => button.classList.remove('active'));\n return;\n }\n\n const panel = featurePanels[activeFeaturePanel];\n featurePanel.classList.add('open');\n featurePanel.style.setProperty('--feature-panel-width', featurePanelWidth + 'px');\n featurePanelTitle.textContent = typeof panel.title === 'function' ? panel.title() : panel.title;\n featurePanelBody.innerHTML = panel.render();\n railButtons.forEach(button => {\n button.classList.toggle('active', button.dataset.panel === activeFeaturePanel);\n });\n\n if (preserveLogSearchFocus) {\n const nextSearchInput = featurePanelBody.querySelector('.log-input');\n if (nextSearchInput) {\n nextSearchInput.focus();\n if (preservedSelectionStart !== null && preservedSelectionEnd !== null && typeof nextSearchInput.setSelectionRange === 'function') {\n nextSearchInput.setSelectionRange(preservedSelectionStart, preservedSelectionEnd);\n }\n }\n }\n }\n\n function toggleFeaturePanel(panelId) {\n activeFeaturePanel = activeFeaturePanel === panelId ? null : panelId;\n renderFeaturePanel();\n }\n\n window.setLogPanelScope = async (scope) => {\n logPanelScope = scope === 'all' ? 'all' : 'current';\n await loadLogs(true);\n renderFeaturePanel();\n };\n\n window.updateLogFilter = (key, value) => {\n logFilters[key] = value;\n renderFeaturePanel();\n };\n\n function closeAgentContextMenu() {\n agentContextMenu.classList.remove('open');\n contextMenuAgentId = null;\n }\n\n function openAgentContextMenu(agentId, x, y, canDelete) {\n contextMenuAgentId = canDelete ? agentId : null;\n deleteAgentAction.disabled = !canDelete;\n\n const margin = 8;\n agentContextMenu.classList.add('open');\n agentContextMenu.style.left = '0px';\n agentContextMenu.style.top = '0px';\n\n const rect = agentContextMenu.getBoundingClientRect();\n const maxLeft = window.innerWidth - rect.width - margin;\n const maxTop = window.innerHeight - rect.height - margin;\n agentContextMenu.style.left = Math.max(margin, Math.min(x, maxLeft)) + 'px';\n agentContextMenu.style.top = Math.max(margin, Math.min(y, maxTop)) + 'px';\n }\n\n railButtons.forEach(button => {\n button.addEventListener('click', () => {\n toggleFeaturePanel(button.dataset.panel);\n if (button.dataset.panel === 'logs' && activeFeaturePanel === 'logs') {\n loadLogs(true).catch((error) => console.error('Failed to load logs:', error));\n } else if (button.dataset.panel === 'mcp' && activeFeaturePanel === 'mcp') {\n loadMcpInfo(true).catch((error) => console.error('Failed to load MCP info:', error));\n }\n });\n });\n\n themeToggle.addEventListener('click', () => {\n applyTheme(currentTheme === 'light' ? 'dark' : 'light');\n });\n\n languageToggle.addEventListener('click', () => {\n currentLanguage = currentLanguage === 'zh' ? 'en' : 'zh';\n applyLanguage();\n });\n\n featurePanelResizer.addEventListener('mousedown', (event) => {\n if (!featurePanel.classList.contains('open')) return;\n\n event.preventDefault();\n\n const handleMouseMove = (moveEvent) => {\n const nextWidth = window.innerWidth - moveEvent.clientX - 56;\n featurePanelWidth = Math.max(240, Math.min(640, nextWidth));\n featurePanel.style.setProperty('--feature-panel-width', featurePanelWidth + 'px');\n };\n\n const handleMouseUp = () => {\n window.removeEventListener('mousemove', handleMouseMove);\n window.removeEventListener('mouseup', handleMouseUp);\n };\n\n window.addEventListener('mousemove', handleMouseMove);\n window.addEventListener('mouseup', handleMouseUp);\n });\n\n\n function formatError(data) {\n const text = typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data);\n return \\`<div class=\"tool-error\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n <span>\\${escapeHtml(text)}</span>\n </div>\\`;\n }\n\n function interpolateTemplate(template, data) {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => {\n const value = data[key];\n return value !== undefined ? String(value) : \\`{{\\${key}}}\\`;\n });\n }\n\n function applyTemplate(template, data, success = true, args = {}) {\n if (typeof template === 'function') {\n return template(data, success, args);\n }\n // 处理内联模板对象 { call: ..., result: ... }\n if (typeof template === 'object' && template !== null) {\n const fn = template.result || template.call;\n if (typeof fn === 'function') {\n return fn(data, success, args);\n }\n if (typeof fn === 'string') {\n return interpolateTemplate(fn, data);\n }\n }\n return interpolateTemplate(template, data);\n }\n\n function parseToolResult(content) {\n try {\n const json = JSON.parse(content);\n if (json && typeof json === 'object' && 'success' in json && 'result' in json) {\n let data = json.result;\n // Try to unwrap double-encoded JSON strings\n if (typeof data === 'string') {\n try {\n if (data.trim().startsWith('\"') || data.trim().startsWith('{') || data.trim().startsWith('[')) {\n const parsed = JSON.parse(data);\n data = parsed;\n }\n } catch (e) {\n // Not a JSON string, keep as is\n }\n }\n return { success: json.success, data: data };\n }\n return { success: true, data: content };\n } catch (e) {\n return { success: true, data: content };\n }\n }\n\n /**\n * 根据模板名解析文件路径\n * 优先级:Feature 模板 > 系统模板 > 兜底\n */\n const self = this;\n\n // 系统默认模板映射(兜底)\n // 格式:featureName/templateName\n // 注意:这些映射仅在 FEATURE_TEMPLATE_MAP 中没有找到时使用\n // 新增 feature 时应确保 feature 正确实现了 getPackageInfo() 和 getTemplateNames()\n const SYSTEM_TEMPLATE_MAP = {\n // SubAgent Feature\n 'agent-spawn': 'subagent/agent-spawn',\n 'agent-list': 'subagent/agent-list',\n 'agent-send': 'subagent/agent-send',\n 'agent-close': 'subagent/agent-close',\n 'wait': 'subagent/wait',\n // Skill Feature\n 'skill': 'skill/skill',\n 'invoke_skill': 'skill/skill',\n // OpencodeBasic Feature\n 'read': 'opencode-basic/read',\n 'write': 'opencode-basic/write',\n 'edit': 'opencode-basic/edit',\n 'ls': 'opencode-basic/ls',\n 'glob': 'opencode-basic/glob',\n 'grep': 'opencode-basic/grep',\n // Todo Feature\n 'task-create': 'todo/task-create',\n 'task-list': 'todo/task-list',\n 'task-get': 'todo/task-get',\n 'task-update': 'todo/task-update',\n 'task-clear': 'todo/task-clear',\n // MCP Feature\n 'mcp-tool': 'mcp/mcp-tool',\n 'mcp-result': 'mcp/mcp-tool',\n // UserInput Feature\n 'user-input': 'user-input/user-input',\n };\n\n function resolveTemplatePath(templateName) {\n // 1. 优先查找 Feature 模板(从后端注入的动态数据)\n if (FEATURE_TEMPLATE_MAP[templateName]) {\n return FEATURE_TEMPLATE_MAP[templateName];\n }\n\n // 2. 使用系统默认映射(统一 URL 格式)\n if (SYSTEM_TEMPLATE_MAP[templateName]) {\n const mapped = SYSTEM_TEMPLATE_MAP[templateName];\n // 系统内置模板使用 /template/agentdev/{feature}/{template}.render.js\n return '/template/agentdev/' + mapped + '.render.js';\n }\n\n // 3. 兜底:返回 null,让调用者等待或使用默认模板\n // 不再盲目生成错误的URL,而是等待 FEATURE_TEMPLATE_MAP 加载完成\n console.warn('[Viewer] Template \"' + templateName + '\" not found in FEATURE_TEMPLATE_MAP or SYSTEM_TEMPLATE_MAP, waiting...');\n return null;\n }\n\n /**\n * 异步加载模板\n * 支持从 Feature 目录或系统目录加载\n * 如果加载失败,回退到内置模板\n */\n async function loadTemplate(templateName, retryCount = 0) {\n if (templateCache.has(templateName)) {\n return templateCache.get(templateName);\n }\n\n // 优先检查内置模板(json 是内置的)\n if (RENDER_TEMPLATES[templateName]) {\n templateCache.set(templateName, RENDER_TEMPLATES[templateName]);\n return RENDER_TEMPLATES[templateName];\n }\n\n try {\n const path = resolveTemplatePath(templateName);\n\n // 如果 path 为 null,说明 FEATURE_TEMPLATE_MAP 还未加载完成\n if (!path) {\n // 最多重试 3 次,每次等待 500ms\n if (retryCount < 3) {\n console.log('[Viewer] Waiting for FEATURE_TEMPLATE_MAP to load... (attempt ' + (retryCount + 1) + ')');\n await new Promise(resolve => setTimeout(resolve, 500));\n // 重新加载模板映射\n await loadFeatureTemplateMap();\n return loadTemplate(templateName, retryCount + 1);\n }\n console.warn('[Viewer] Template \"' + templateName + '\" not found after retries');\n // 回退到内置 json 模板\n if (RENDER_TEMPLATES['json']) {\n return RENDER_TEMPLATES['json'];\n }\n return null;\n }\n\n // 统一使用 URL 方式加载模板\n // Feature 模板: /template/agentdev/shell/bash.render.js\n const module = await import(path);\n\n // 1. 优先使用 default export(Feature 模板)\n let template = module.default;\n if (template) {\n templateCache.set(templateName, template);\n return template;\n }\n\n // 2. 尝试从 TEMPLATES 对象获取(系统模板)\n if (module.TEMPLATES && module.TEMPLATES[templateName]) {\n template = module.TEMPLATES[templateName];\n templateCache.set(templateName, template);\n return template;\n }\n\n console.warn('[Viewer Worker] 模板 \"' + templateName + '\" 在文件中未找到');\n return null;\n } catch (e) {\n console.warn('[Viewer Worker] 加载模板失败: ' + templateName, e);\n // 回退到内置 json 模板\n if (RENDER_TEMPLATES['json']) {\n return RENDER_TEMPLATES['json'];\n }\n return null;\n }\n }\n\n function getToolRenderTemplate(toolName) {\n const config = toolRenderConfigs[toolName];\n const callTemplateName = (config?.render?.call) || 'json';\n const resultTemplateName = (config?.render?.result) || 'json';\n\n const callIsInline = callTemplateName === '__inline__';\n const resultIsInline = resultTemplateName === '__inline__';\n\n let callTemplate, resultTemplate;\n\n if (callIsInline) {\n callTemplate = config?.render?.inlineCall;\n } else {\n // 优先从缓存读取\n const cached = templateCache.get(callTemplateName);\n callTemplate = cached?.call || RENDER_TEMPLATES['json'].call;\n }\n\n if (resultIsInline) {\n resultTemplate = config?.render?.inlineResult;\n } else {\n const cached = templateCache.get(resultTemplateName);\n resultTemplate = cached?.result || RENDER_TEMPLATES['json'].result;\n }\n\n return {\n call: callTemplate,\n result: resultTemplate,\n isInlineCall: callIsInline,\n isInlineResult: resultIsInline,\n };\n }\n\n function getToolDisplayName(toolName) {\n return TOOL_NAMES[toolName] || toolName;\n }\n\n async function loadAgents() {\n try {\n const res = await fetch('/api/agents');\n const data = await res.json();\n allAgents = data.agents || [];\n\n renderAgentList();\n renderFeaturePanel();\n\n if (data.currentAgentId && data.currentAgentId !== currentAgentId) {\n currentAgentId = data.currentAgentId;\n setFollowLatest(true);\n await loadAgentData(currentAgentId);\n }\n } catch (e) {\n console.error('Failed to load agents:', e);\n }\n }\n\n function renderAgentList() {\n agentList.innerHTML = allAgents.map(a => {\n const isActive = a.id === currentAgentId;\n const isConnected = a.connected !== false;\n // Agent ID 格式:agent-{序号}-{进程PID}\n const parts = a.id.split('-');\n const agentNum = parts[1] || '?';\n const pid = parts[2] || '';\n const displayId = pid ? '#'.concat(agentNum, ' (', pid, ')') : '#'.concat(agentNum);\n return \\`\n <div\n class=\"agent-item \\${isActive ? 'active' : ''} \\${isConnected ? '' : 'disconnected'}\"\n onclick=\"switchAgent('\\${a.id}')\"\n oncontextmenu=\"openAgentActions(event, '\\${a.id}')\"\n >\n <div class=\"agent-name\">\\${escapeHtml(a.name)}</div>\n <div class=\"agent-meta\">\n <span class=\"agent-status\">\n <span class=\"agent-status-dot\"></span>\n <span>\\${isConnected ? escapeHtml(t('status_connected')) : escapeHtml(t('status_disconnected'))}</span>\n </span>\n · \\${displayId} · \\${a.messageCount} \\${escapeHtml(t('feature_messages'))}\n </div>\n </div>\n \\`;\n }).join('');\n \n const activeAgent = allAgents.find(a => a.id === currentAgentId);\n if (activeAgent) {\n currentAgentTitle.textContent = activeAgent.name;\n } else {\n currentAgentTitle.textContent = t('page_title');\n }\n }\n\n window.switchAgent = async (newAgentId) => {\n if (newAgentId === currentAgentId) return;\n closeAgentContextMenu();\n try {\n const res = await fetch('/api/agents/current', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ agentId: newAgentId })\n });\n if (res.ok) {\n currentAgentId = newAgentId;\n setFollowLatest(true);\n await loadAgentData(newAgentId);\n renderAgentList(); // Update active state\n }\n } catch (e) {\n console.error('Failed to switch agent:', e);\n }\n };\n\n window.openAgentActions = (event, agentId) => {\n event.preventDefault();\n const agent = allAgents.find(item => item.id === agentId);\n if (!agent) return;\n openAgentContextMenu(agentId, event.clientX, event.clientY, agent.connected === false);\n };\n\n deleteAgentAction.addEventListener('click', async () => {\n if (!contextMenuAgentId) return;\n\n const agent = allAgents.find(item => item.id === contextMenuAgentId);\n if (!agent || agent.connected !== false) {\n closeAgentContextMenu();\n return;\n }\n\n const confirmed = window.confirm(t('delete_confirm'));\n if (!confirmed) {\n closeAgentContextMenu();\n return;\n }\n\n try {\n const res = await fetch(\\`/api/agents/\\${contextMenuAgentId}\\`, { method: 'DELETE' });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n throw new Error(data.error || t('delete_failed_generic'));\n }\n\n closeAgentContextMenu();\n await loadAgents();\n\n if (data.currentAgentId && data.currentAgentId !== currentAgentId) {\n currentAgentId = data.currentAgentId;\n await loadAgentData(currentAgentId);\n } else if (!data.currentAgentId) {\n currentAgentId = null;\n currentMessages = [];\n setCurrentLogs([]);\n setCurrentHookInspector({ lifecycleOrder: [], features: [], hooks: [] });\n setCurrentOverviewSnapshot(getEmptyOverviewSnapshot());\n container.innerHTML = getEmptyStateHtml();\n setFollowLatest(true);\n currentAgentTitle.textContent = t('page_title');\n }\n } catch (e) {\n closeAgentContextMenu();\n window.alert(t('delete_failed') + (e && e.message ? e.message : e));\n }\n });\n\n document.addEventListener('click', (event) => {\n if (!agentContextMenu.contains(event.target)) {\n closeAgentContextMenu();\n }\n });\n\n window.addEventListener('resize', () => {\n closeAgentContextMenu();\n featurePanelWidth = Math.max(240, Math.min(640, featurePanelWidth));\n if (featurePanel.classList.contains('open')) {\n featurePanel.style.setProperty('--feature-panel-width', featurePanelWidth + 'px');\n }\n });\n window.addEventListener('scroll', closeAgentContextMenu, true);\n container.addEventListener('wheel', markManualScrollIntent, { passive: true });\n container.addEventListener('touchstart', markManualScrollIntent, { passive: true });\n container.addEventListener('keydown', (event) => {\n if (['ArrowUp', 'PageUp', 'Home', ' '].includes(event.key)) {\n markManualScrollIntent();\n }\n });\n container.addEventListener('scroll', () => {\n if (suppressFollowScrollEvent || !followLatestEnabled) {\n return;\n }\n if (!isNearBottom() && hasRecentManualScrollIntent()) {\n setFollowLatest(false);\n }\n });\n followLatestButton.addEventListener('click', () => {\n setFollowLatest(true, { scroll: true, behavior: 'smooth' });\n });\n\n async function loadLogs(forceRender = false) {\n try {\n const params = new URLSearchParams({\n scope: logPanelScope,\n });\n if (currentAgentId) {\n params.set('agentId', currentAgentId);\n }\n\n const res = await fetch('/api/logs?' + params.toString());\n if (!res.ok) {\n throw new Error('Failed to fetch logs');\n }\n const data = await res.json();\n const nextLogs = data.logs || [];\n const nextSignature = JSON.stringify({\n count: nextLogs.length,\n last: nextLogs.length > 0 ? nextLogs[nextLogs.length - 1].id : null,\n });\n\n if (nextSignature !== currentLogsSignature) {\n setCurrentLogs(nextLogs);\n if (activeFeaturePanel === 'logs') {\n renderFeaturePanel();\n }\n } else if (forceRender && activeFeaturePanel === 'logs') {\n renderFeaturePanel();\n }\n } catch (e) {\n if (forceRender && activeFeaturePanel === 'logs') {\n setCurrentLogs([]);\n renderFeaturePanel();\n }\n }\n }\n\n async function loadMcpInfo(forceRender = false) {\n try {\n const res = await fetch('/api/mcp-info');\n if (!res.ok) {\n throw new Error('Failed to fetch MCP info');\n }\n const data = await res.json();\n setCurrentMcpInfo(data);\n if (forceRender && activeFeaturePanel === 'mcp') {\n renderFeaturePanel();\n }\n } catch (e) {\n console.error('Failed to load MCP info:', e);\n if (forceRender && activeFeaturePanel === 'mcp') {\n renderFeaturePanel();\n }\n }\n }\n\n async function loadAgentData(agentId) {\n try {\n const [msgsRes, toolsRes, hooksRes, overviewRes] = await Promise.all([\n fetch(\\`/api/agents/\\${agentId}/messages\\`),\n fetch(\\`/api/agents/\\${agentId}/tools\\`),\n fetch(\\`/api/agents/\\${agentId}/hooks\\`),\n fetch(\\`/api/agents/\\${agentId}/overview\\`)\n ]);\n\n const msgsData = await msgsRes.json();\n const tools = await toolsRes.json();\n setCurrentHookInspector(await hooksRes.json());\n setCurrentOverviewSnapshot(await overviewRes.json());\n\n currentMessages = msgsData.messages || [];\n toolRenderConfigs = {};\n TOOL_NAMES = {};\n\n const DEFAULT_DISPLAY_NAMES = {\n // 系统工具\n run_shell_command: 'Bash',\n read_file: 'Read File',\n write_file: 'Write File',\n list_directory: 'List',\n web_fetch: 'Web',\n calculator: 'Calc',\n invoke_skill: 'Invoke Skill',\n spawn_agent: 'Spawn Agent',\n list_agents: 'List Agents',\n send_to_agent: 'Send to Agent',\n close_agent: 'Close Agent',\n // Opencode 工具\n read: 'Read',\n write: 'Write',\n edit: 'Edit',\n glob: 'Glob',\n grep: 'Grep',\n ls: 'LS',\n };\n\n for (const tool of tools) {\n toolRenderConfigs[tool.name] = tool;\n TOOL_NAMES[tool.name] = DEFAULT_DISPLAY_NAMES[tool.name] || tool.name;\n }\n\n // 预加载所有需要的模板\n const templatesToLoad = new Set();\n\n for (const tool of tools) {\n const renderConfig = tool.render;\n if (renderConfig) {\n if (typeof renderConfig === 'string') {\n templatesToLoad.add(renderConfig);\n } else if (typeof renderConfig === 'object') {\n if (renderConfig.call && renderConfig.call !== '__inline__') {\n templatesToLoad.add(renderConfig.call);\n }\n if (renderConfig.result && renderConfig.result !== '__inline__') {\n templatesToLoad.add(renderConfig.result);\n }\n }\n }\n }\n\n // 并行加载所有模板\n const loadPromises = Array.from(templatesToLoad).map(name => loadTemplate(name));\n await Promise.all(loadPromises);\n\n render(currentMessages);\n setFollowLatest(true, { scroll: true, behavior: 'auto' });\n if (activeFeaturePanel === 'logs') {\n await loadLogs(true);\n }\n renderFeaturePanel();\n } catch (e) {\n console.error('Failed to load agent data:', e);\n }\n }\n\n async function poll() {\n try {\n // 定期检查并重新加载 Feature 模板映射(如果为空)\n if (Object.keys(FEATURE_TEMPLATE_MAP).length === 0) {\n await reloadFeatureTemplateMap();\n }\n\n if (!currentAgentId) {\n await loadAgents();\n if (activeFeaturePanel === 'logs' && logPanelScope === 'all') {\n await loadLogs();\n }\n setTimeout(poll, 1000);\n return;\n }\n\n // 并行请求消息、通知和输入请求\n const [msgsRes, notifRes, connectionRes, inputRes, overviewRes] = await Promise.all([\n fetch(\\`/api/agents/\\${currentAgentId}/messages\\`),\n fetch(\\`/api/agents/\\${currentAgentId}/notification\\`),\n fetch(\\`/api/agents/\\${currentAgentId}/connection\\`),\n fetch(\\`/api/agents/\\${currentAgentId}/input-requests\\`),\n fetch(\\`/api/agents/\\${currentAgentId}/overview\\`),\n ]);\n\n const connectionData = await connectionRes.json();\n setConnectionStatus(!!connectionData.connected);\n\n const data = await msgsRes.json();\n const messages = data.messages || [];\n\n // 处理通知状态\n const notifData = await notifRes.json();\n updateNotificationStatus(notifData);\n\n const nextOverview = normalizeOverviewSnapshot(await overviewRes.json());\n const nextOverviewSignature = getOverviewSignature(nextOverview);\n if (nextOverviewSignature !== currentOverviewSignature) {\n currentOverviewSnapshot = nextOverview;\n currentOverviewSignature = nextOverviewSignature;\n if (activeFeaturePanel === 'workspace') {\n renderFeaturePanel();\n }\n }\n\n // 处理输入请求(只在变化时重新渲染)\n const inputRequests = await inputRes.json();\n if (JSON.stringify(inputRequests) !== JSON.stringify(window.lastInputRequests || [])) {\n window.lastInputRequests = inputRequests;\n renderInputRequests(inputRequests);\n updateRollbackActionVisibility();\n }\n\n if (messages.length !== currentMessages.length || messages.length === 0) {\n if (messages.length > currentMessages.length) {\n // 有新消息:只追加新的\n const newMessages = messages.slice(currentMessages.length);\n currentMessages = messages;\n appendNewMessages(newMessages, currentMessages.length - newMessages.length);\n } else if (messages.length < currentMessages.length) {\n // 消息减少:完全重建(极少情况)\n currentMessages = messages;\n render(messages);\n } else {\n // 长度相同但内容可能是初始加载:完全重建\n currentMessages = messages;\n render(messages);\n }\n } else {\n const lastMsgChanged = messages.length > 0 &&\n JSON.stringify(messages[messages.length - 1]) !== JSON.stringify(currentMessages[currentMessages.length - 1]);\n if (lastMsgChanged) {\n // 最后一条消息变化:替换最后一条(避免滚动重置)\n currentMessages = messages;\n updateLastMessage(messages[messages.length - 1]);\n }\n }\n\n // Also refresh agent list occasionally to get new agents\n if (Math.random() < 0.1) {\n const agentsRes = await fetch('/api/agents');\n const agentsData = await agentsRes.json();\n if (JSON.stringify(agentsData.agents) !== JSON.stringify(allAgents)) {\n allAgents = agentsData.agents || [];\n renderAgentList();\n renderFeaturePanel();\n }\n }\n\n if (activeFeaturePanel) {\n if (activeFeaturePanel === 'logs') {\n await loadLogs();\n } else {\n const hooksRes = await fetch(\\`/api/agents/\\${currentAgentId}/hooks\\`);\n const nextHookInspector = normalizeHookInspector(await hooksRes.json());\n const nextSignature = getHookInspectorSignature(nextHookInspector);\n if (nextSignature !== currentHookInspectorSignature) {\n currentHookInspector = nextHookInspector;\n currentHookInspectorSignature = nextSignature;\n renderFeaturePanel();\n } else if (activeFeaturePanel === 'inspector') {\n renderFeaturePanel();\n }\n }\n }\n\n } catch (e) {\n setConnectionStatus(false);\n }\n setTimeout(poll, 100);\n }\n\n // 通知状态更新\n function updateNotificationStatus(notifData) {\n const statusEl = document.getElementById('notification-status');\n const phaseEl = document.getElementById('notification-phase');\n const charCountEl = document.getElementById('notification-char-count');\n\n if (!notifData.state) {\n statusEl.style.display = 'none';\n return;\n }\n\n const { type, data } = notifData.state;\n\n if (type === 'llm.char_count') {\n statusEl.style.display = 'flex';\n statusEl.classList.add('active');\n\n const phaseNames = {\n 'thinking': t('phase_thinking'),\n 'content': t('phase_content'),\n 'tool_calling': t('phase_tool_calling')\n };\n phaseEl.textContent = phaseNames[data.phase] || data.phase;\n charCountEl.textContent = data.charCount.toLocaleString();\n } else if (type === 'llm.complete') {\n statusEl.style.display = 'none';\n statusEl.classList.remove('active');\n } else {\n statusEl.style.display = 'none';\n }\n }\n\n // 渲染输入请求\n function renderInputRequests(requests) {\n const container = document.getElementById('user-input-container');\n if (!container) return;\n currentInputRequests = requests;\n const hasChoiceRequest = Array.isArray(requests) && requests.some(isChoiceInputRequest);\n\n // 清空现有内容\n container.innerHTML = '';\n container.classList.toggle('choice-input-active', hasChoiceRequest);\n container.classList.remove('choice-collapsed');\n container.onclick = hasChoiceRequest\n ? function(event) {\n if (event.target === container) {\n collapsePrimaryChoiceRequest();\n }\n }\n : null;\n\n for (const req of requests) {\n if (isChoiceInputRequest(req)) {\n renderChoiceInputRequest(container, req);\n continue;\n }\n\n const card = document.createElement('div');\n card.className = 'user-input-card';\n const actionsHtml = Array.isArray(req.actions) && req.actions.length > 0\n ? '<div class=\"user-input-actions\">' + req.actions.map(action =>\n '<button class=\"user-input-action ' + escapeHtml(action.variant || 'secondary') + '\" onclick=\"submitInputAction(\\\\'' + req.requestId + '\\\\', \\\\'' + escapeHtml(action.id) + '\\\\')\">' + escapeHtml(action.label) + '</button>'\n ).join('') + '</div>'\n : '';\n card.innerHTML = \\`\n <textarea class=\"user-input-textarea\" rows=\"1\" id=\"input-\\${req.requestId}\"\n onkeydown=\"handleInputKey(event, '\\${req.requestId}')\"\n oninput=\"autoResize(this)\"\n placeholder=\"\\${escapeHtml(req.placeholder || t('input_placeholder'))}\"></textarea>\n <div class=\"user-input-footer\">\n \\${actionsHtml}\n </div>\n \\`;\n container.appendChild(card);\n \n // Auto-focus\n setTimeout(() => {\n const el = document.getElementById(\\`input-\\${req.requestId}\\`);\n if(el) {\n if (typeof req.initialValue === 'string' && req.initialValue.length > 0) {\n el.value = req.initialValue;\n }\n el.focus();\n const end = el.value.length;\n if (typeof el.setSelectionRange === 'function') {\n el.setSelectionRange(end, end);\n }\n autoResize(el);\n }\n }, 50);\n }\n }\n\n function isChoiceInputRequest(req) {\n return !!req && req.mode === 'choices' && Array.isArray(req.questions) && req.questions.length > 0;\n }\n\n function getChoiceRequestById(requestId) {\n return (currentInputRequests || []).find(req => req.requestId === requestId) || null;\n }\n\n function getChoiceState(requestId) {\n if (!choiceInputState[requestId]) {\n choiceInputState[requestId] = {\n questionIndex: 0,\n answers: [],\n selectedIndex: 0,\n selectedIndexByQuestion: {},\n customTextByQuestion: {},\n supplementTextByOption: {},\n collapsed: false,\n };\n }\n return choiceInputState[requestId];\n }\n\n function getChoiceOptionCount(question) {\n const optionCount = Array.isArray(question?.options) ? Math.min(question.options.length, 4) : 0;\n return optionCount + (question?.allowCustom ? 1 : 0);\n }\n\n function buildChoiceAnswer(req, state, questionIndex) {\n const question = req?.questions?.[questionIndex] || {};\n const options = Array.isArray(question.options) ? question.options.slice(0, 4) : [];\n const selectedIndex = state.selectedIndexByQuestion?.[question.id] ?? (questionIndex === state.questionIndex ? state.selectedIndex : 0);\n const isCustom = question.allowCustom && selectedIndex >= options.length;\n if (isCustom) {\n return {\n questionId: question.id,\n customText: (state.customTextByQuestion[question.id] || '').trim(),\n };\n }\n const selectedOption = options[selectedIndex];\n const supplementKey = question.id + ':' + (selectedOption?.id || '');\n const supplementText = selectedOption?.allowSupplement\n ? (state.supplementTextByOption?.[supplementKey] || '').trim()\n : undefined;\n return {\n questionId: question.id,\n optionId: selectedOption?.id,\n supplementText: supplementText || undefined,\n };\n }\n\n function rememberCurrentChoice(req, state) {\n const question = req?.questions?.[state.questionIndex] || {};\n if (!question.id) return;\n state.selectedIndexByQuestion[question.id] = state.selectedIndex || 0;\n state.answers[state.questionIndex] = buildChoiceAnswer(req, state, state.questionIndex);\n }\n\n function renderChoiceInputRequest(container, req) {\n const state = getChoiceState(req.requestId);\n const questions = Array.isArray(req.questions) ? req.questions : [];\n if (state.collapsed) {\n container.classList.add('choice-collapsed');\n const mini = document.createElement('button');\n mini.className = 'user-choice-mini';\n mini.type = 'button';\n mini.setAttribute('onclick', \\`expandChoiceRequest('\\${req.requestId}')\\`);\n mini.innerHTML = \\`\n <span class=\"user-choice-mini-title\">\\${escapeHtml(req.prompt || '等待你的选择')}</span>\n <span class=\"user-choice-mini-meta\">\\${Math.min((state.questionIndex || 0) + 1, questions.length)} / \\${questions.length}</span>\n \\`;\n container.appendChild(mini);\n return;\n }\n\n container.classList.remove('choice-collapsed');\n const questionIndex = Math.max(0, Math.min(state.questionIndex || 0, questions.length - 1));\n state.questionIndex = questionIndex;\n const question = questions[questionIndex] || {};\n const options = Array.isArray(question.options) ? question.options.slice(0, 4) : [];\n const hasCustom = !!question.allowCustom;\n const optionCount = options.length + (hasCustom ? 1 : 0);\n state.selectedIndex = Math.max(0, Math.min(state.selectedIndexByQuestion?.[question.id] ?? state.selectedIndex ?? 0, Math.max(0, optionCount - 1)));\n\n const card = document.createElement('div');\n card.className = 'user-input-card user-choice-card';\n card.tabIndex = 0;\n card.setAttribute('onkeydown', \\`handleChoiceKey(event, '\\${req.requestId}')\\`);\n\n const optionHtml = options.map((option, index) => {\n const isActive = index === state.selectedIndex;\n const supplementKey = question.id + ':' + (option.id || '');\n const supplementText = state.supplementTextByOption?.[supplementKey] || '';\n const showSupplement = isActive && option.allowSupplement;\n const supplementLabel = option.supplementLabel || '';\n const supplementPlaceholder = option.supplementPlaceholder || '';\n return \\`\n <div>\n <button class=\"user-choice-option \\${isActive ? 'active' : ''}\" type=\"button\" onclick=\"selectChoiceOption('\\${req.requestId}', \\${index})\">\n <span class=\"user-choice-key\">\\${index + 1}</span>\n <span>\n <span class=\"user-choice-label\">\\${escapeHtml(option.label || option.id || ('选项 ' + (index + 1)))}</span>\n \\${option.description ? \\`<span class=\"user-choice-description\">\\${escapeHtml(option.description)}</span>\\` : ''}\n </span>\n </button>\n <div class=\"user-choice-supplement \\${showSupplement ? 'active' : ''}\">\n \\${supplementLabel ? \\`<div class=\"user-choice-supplement-label\">\\${escapeHtml(supplementLabel)}</div>\\` : ''}\n <textarea id=\"choice-supplement-\\${req.requestId}-\\${index}\" rows=\"2\"\n oninput=\"updateChoiceSupplementText('\\${req.requestId}', '\\${supplementKey}', this.value); autoResize(this)\"\n onkeydown=\"handleChoiceCustomKey(event, '\\${req.requestId}')\"\n placeholder=\"\\${escapeHtml(supplementPlaceholder || '补充说明(可选)')}\">\\${escapeHtml(supplementText)}</textarea>\n </div>\n </div>\n \\`;\n }).join('');\n\n const customIndex = options.length;\n const customActive = hasCustom && state.selectedIndex === customIndex;\n const customText = state.customTextByQuestion[question.id] || '';\n const customHtml = hasCustom ? \\`\n <button class=\"user-choice-option \\${customActive ? 'active' : ''}\" type=\"button\" onclick=\"selectChoiceOption('\\${req.requestId}', \\${customIndex})\">\n <span class=\"user-choice-key\">\\${customIndex + 1}</span>\n <span>\n <span class=\"user-choice-label\">\\${escapeHtml(question.customLabel || '其他,我想补充')}</span>\n <span class=\"user-choice-description\">选择后可以直接输入想说的话</span>\n </span>\n </button>\n <div class=\"user-choice-custom \\${customActive ? 'active' : ''}\">\n <textarea id=\"choice-custom-\\${req.requestId}\" rows=\"2\"\n oninput=\"updateChoiceCustomText('\\${req.requestId}', this.value); autoResize(this)\"\n onkeydown=\"handleChoiceCustomKey(event, '\\${req.requestId}')\"\n placeholder=\"\\${escapeHtml(question.customPlaceholder || '输入你的补充内容')}\">\\${escapeHtml(customText)}</textarea>\n </div>\n \\` : '';\n\n card.innerHTML = \\`\n <div class=\"user-choice-topline\">\n <div class=\"user-choice-title\">\\${escapeHtml(req.prompt || '需要你做个选择')}</div>\n <div class=\"user-choice-progress\">\\${questionIndex + 1} / \\${questions.length}</div>\n <button class=\"user-choice-close\" type=\"button\" title=\"临时收起\" onclick=\"collapseChoiceRequest('\\${req.requestId}')\">×</button>\n </div>\n <div class=\"user-choice-question\">\\${escapeHtml(question.question || '')}</div>\n <div class=\"user-choice-options\">\n \\${optionHtml}\n \\${customHtml}\n </div>\n <div class=\"user-choice-footer\">\n <span>↑↓ 选项,←→ 题目,Enter 确认</span>\n <button class=\"user-choice-submit\" type=\"button\" onclick=\"confirmChoiceQuestion('\\${req.requestId}')\">\\${questionIndex + 1 === questions.length ? '提交' : '下一题'}</button>\n </div>\n \\`;\n\n container.appendChild(card);\n setTimeout(() => {\n const customInput = customActive ? document.getElementById(\\`choice-custom-\\${req.requestId}\\`) : null;\n const activeOption = options[state.selectedIndex];\n const supplementInput = !customActive && activeOption?.allowSupplement\n ? document.getElementById('choice-supplement-' + req.requestId + '-' + state.selectedIndex)\n : null;\n const target = customInput || supplementInput || card;\n target.focus();\n if (customInput || supplementInput) {\n const el = customInput || supplementInput;\n const end = el.value.length;\n el.setSelectionRange(end, end);\n autoResize(el);\n }\n }, 30);\n }\n\n function rerenderChoiceRequest(requestId) {\n renderInputRequests(currentInputRequests || []);\n }\n\n window.selectChoiceOption = function(requestId, optionIndex) {\n const req = getChoiceRequestById(requestId);\n const state = getChoiceState(requestId);\n state.selectedIndex = optionIndex;\n const question = req?.questions?.[state.questionIndex];\n if (question?.id) {\n state.selectedIndexByQuestion[question.id] = optionIndex;\n }\n rerenderChoiceRequest(requestId);\n };\n\n window.collapseChoiceRequest = function(requestId) {\n const state = getChoiceState(requestId);\n const req = getChoiceRequestById(requestId);\n rememberCurrentChoice(req, state);\n state.collapsed = true;\n rerenderChoiceRequest(requestId);\n };\n\n window.expandChoiceRequest = function(requestId) {\n const state = getChoiceState(requestId);\n state.collapsed = false;\n rerenderChoiceRequest(requestId);\n };\n\n function collapsePrimaryChoiceRequest() {\n const request = (currentInputRequests || []).find(isChoiceInputRequest);\n if (request) {\n window.collapseChoiceRequest(request.requestId);\n }\n }\n\n window.updateChoiceCustomText = function(requestId, value) {\n const req = getChoiceRequestById(requestId);\n const state = getChoiceState(requestId);\n const question = req?.questions?.[state.questionIndex];\n if (question?.id) {\n state.customTextByQuestion[question.id] = value;\n }\n };\n\n window.updateChoiceSupplementText = function(requestId, supplementKey, value) {\n const state = getChoiceState(requestId);\n if (!state.supplementTextByOption) {\n state.supplementTextByOption = {};\n }\n state.supplementTextByOption[supplementKey] = value;\n };\n\n window.handleChoiceKey = function(event, requestId) {\n const req = getChoiceRequestById(requestId);\n if (!req) return;\n const state = getChoiceState(requestId);\n const question = req.questions[state.questionIndex] || {};\n const optionCount = getChoiceOptionCount(question);\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n state.selectedIndex = Math.min(optionCount - 1, (state.selectedIndex || 0) + 1);\n if (question.id) state.selectedIndexByQuestion[question.id] = state.selectedIndex;\n rerenderChoiceRequest(requestId);\n } else if (event.key === 'ArrowUp') {\n event.preventDefault();\n state.selectedIndex = Math.max(0, (state.selectedIndex || 0) - 1);\n if (question.id) state.selectedIndexByQuestion[question.id] = state.selectedIndex;\n rerenderChoiceRequest(requestId);\n } else if (event.key === 'ArrowRight') {\n event.preventDefault();\n rememberCurrentChoice(req, state);\n state.questionIndex = Math.min(req.questions.length - 1, state.questionIndex + 1);\n state.selectedIndex = state.selectedIndexByQuestion[req.questions[state.questionIndex]?.id] ?? 0;\n rerenderChoiceRequest(requestId);\n } else if (event.key === 'ArrowLeft') {\n event.preventDefault();\n rememberCurrentChoice(req, state);\n state.questionIndex = Math.max(0, state.questionIndex - 1);\n state.selectedIndex = state.selectedIndexByQuestion[req.questions[state.questionIndex]?.id] ?? 0;\n rerenderChoiceRequest(requestId);\n } else if (event.key === 'Enter') {\n event.preventDefault();\n confirmChoiceQuestion(requestId);\n }\n };\n\n window.handleChoiceCustomKey = function(event, requestId) {\n if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey) {\n event.preventDefault();\n confirmChoiceQuestion(requestId);\n }\n };\n\n window.confirmChoiceQuestion = async function(requestId) {\n const req = getChoiceRequestById(requestId);\n if (!req) return;\n const state = getChoiceState(requestId);\n const questions = req.questions || [];\n rememberCurrentChoice(req, state);\n\n // Validate supplement-required for current question\n const currentQuestion = questions[state.questionIndex];\n if (currentQuestion) {\n const currentAnswer = state.answers[state.questionIndex];\n if (currentAnswer && currentAnswer.optionId) {\n const selectedOption = (currentQuestion.options || []).find(o => o.id === currentAnswer.optionId);\n if (selectedOption && selectedOption.allowSupplement && selectedOption.supplementRequired) {\n const supplementKey = currentQuestion.id + ':' + currentAnswer.optionId;\n const supplementValue = (state.supplementTextByOption?.[supplementKey] || '').trim();\n if (!supplementValue) {\n const supplementInput = document.getElementById('choice-supplement-' + requestId + '-' + (currentQuestion.options.indexOf(selectedOption)));\n if (supplementInput) {\n supplementInput.focus();\n supplementInput.style.borderColor = '#dc3545';\n setTimeout(() => { supplementInput.style.borderColor = ''; }, 1500);\n }\n return;\n }\n }\n }\n }\n\n if (state.questionIndex < questions.length - 1) {\n state.questionIndex += 1;\n state.selectedIndex = state.selectedIndexByQuestion[questions[state.questionIndex]?.id] ?? 0;\n rerenderChoiceRequest(requestId);\n return;\n }\n\n const finalAnswers = questions.map((_, index) => state.answers[index] || buildChoiceAnswer(req, state, index));\n const summary = finalAnswers.map((item, index) => {\n const q = questions[index] || {};\n if (item.customText) return \\`\\${q.question || item.questionId}: \\${item.customText}\\`;\n const option = (q.options || []).find(candidate => candidate.id === item.optionId);\n let line = \\`\\${q.question || item.questionId}: \\${option?.label || item.optionId || ''}\\`;\n if (item.supplementText) line += \\` (\\${item.supplementText})\\`;\n return line;\n }).join('\\\\n');\n\n try {\n const res = await fetch(\\`/api/agents/\\${currentAgentId}/input\\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n requestId,\n input: summary,\n response: {\n kind: 'choices',\n choices: finalAnswers,\n text: summary,\n },\n }),\n });\n if (res.ok) {\n delete choiceInputState[requestId];\n poll();\n }\n } catch (e) {\n console.error('提交选择失败:', e);\n }\n };\n\n function syncRollbackActionButtons() {\n const allowRollback = !!getPrimaryInputRequest();\n const rows = container.querySelectorAll('.message-row');\n\n rows.forEach((row, index) => {\n const msg = currentMessages[index];\n const meta = row.querySelector('.message-meta');\n if (!meta) return;\n\n const existingButton = meta.querySelector('.message-action');\n const shouldShow = allowRollback && !!msg && msg.role === 'user';\n\n if (!shouldShow) {\n if (existingButton) {\n existingButton.remove();\n }\n return;\n }\n\n if (existingButton) {\n existingButton.setAttribute('onclick', 'requestRollbackEdit(' + index + ')');\n existingButton.style.display = '';\n return;\n }\n\n const button = document.createElement('button');\n button.className = 'message-action';\n button.type = 'button';\n button.textContent = '编辑此轮';\n button.setAttribute('onclick', 'requestRollbackEdit(' + index + ')');\n meta.appendChild(button);\n });\n }\n\n function updateRollbackActionVisibility() {\n syncRollbackActionButtons();\n }\n\n function autoResize(textarea) {\n textarea.style.height = 'auto';\n textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';\n }\n\n function handleInputKey(event, requestId) {\n if (event.key === 'Enter') {\n if (event.ctrlKey || event.shiftKey) {\n // Ctrl+Enter or Shift+Enter for new line\n // default behavior is new line, but we might want to ensure it works\n return; \n } else {\n // Enter for submit\n event.preventDefault();\n submitInput(requestId);\n }\n }\n }\n\n // 提交输入\n async function submitInput(requestId) {\n const textarea = document.getElementById(\\`input-\\${requestId}\\`);\n const input = textarea ? textarea.value : '';\n\n try {\n const res = await fetch(\\`/api/agents/\\${currentAgentId}/input\\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n requestId,\n input,\n response: {\n kind: 'text',\n text: input,\n },\n })\n });\n if (res.ok) {\n setFollowLatest(true, { scroll: true, behavior: 'smooth' });\n // 刷新输入请求列表\n poll();\n }\n } catch (e) {\n console.error('提交输入失败:', e);\n }\n }\n\n function getPrimaryInputRequest() {\n return Array.isArray(currentInputRequests) && currentInputRequests.length > 0\n ? currentInputRequests[0]\n : null;\n }\n\n function canRollbackMessage(msg) {\n return !!getPrimaryInputRequest() && !!msg && msg.role === 'user';\n }\n\n async function submitInputAction(requestId, actionId, payload = {}) {\n try {\n const res = await fetch(\\`/api/agents/\\${currentAgentId}/input\\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n requestId,\n input: '',\n response: {\n kind: 'action',\n actionId,\n payload,\n },\n }),\n });\n if (res.ok) {\n poll();\n }\n } catch (e) {\n console.error('提交动作失败:', e);\n }\n }\n\n window.requestRollbackEdit = async function(messageIndex) {\n const request = getPrimaryInputRequest();\n if (!request) {\n console.warn('No pending input request available for rollback action');\n return;\n }\n\n const msg = currentMessages[messageIndex];\n if (!msg || msg.role !== 'user') {\n return;\n }\n\n const fallbackCallIndex = currentMessages\n .slice(0, messageIndex + 1)\n .filter(entry => entry.role === 'user')\n .length - 1;\n const callIndex = typeof msg.turn === 'number' ? msg.turn : fallbackCallIndex;\n\n await submitInputAction(request.requestId, 'rollback_to_call', {\n callIndex,\n draftInput: msg.content,\n });\n };\n\n // 生成单条消息的 HTML\n function renderMessage(msg, index) {\n const role = msg.role;\n const msgId = \\`msg-\\${index}\\`;\n let contentHtml = '';\n let metaHtml = \\`<div class=\"role-badge\">\\${role}</div>\\`;\n if (canRollbackMessage(msg)) {\n metaHtml += \\`<button class=\"message-action\" onclick=\"requestRollbackEdit(\\${index})\">编辑此轮</button>\\`;\n }\n\n if (role === 'user' || role === 'system') {\n let style = '';\n let rowClass = role;\n if (role === 'system') {\n const isLong = msg.content.includes('\\\\n') || msg.content.length > 60;\n if (isLong) {\n style = 'text-align: left !important;';\n rowClass += ' long-content';\n }\n contentHtml = \\`<div class=\"message-content markdown-body\" id=\"\\${msgId}\" style=\"\\${style}\">\\${marked.parse(msg.content)}</div>\\`;\n } else {\n contentHtml = \\`<div class=\"message-content markdown-body\" id=\"\\${msgId}\">\\${marked.parse(msg.content)}</div>\\`;\n }\n\n if (role === 'system') {\n return \\`\n <div class=\"message-row \\${rowClass}\">\n <div class=\"message-meta\">\n \\${metaHtml}\n </div>\n \\${contentHtml}\n </div>\n \\`;\n }\n return \\`\n <div class=\"message-row \\${role}\">\n <div class=\"message-meta\">\n \\${metaHtml}\n </div>\n \\${contentHtml}\n </div>\n \\`;\n } else if (role === 'assistant') {\n let innerContent = '';\n\n if (msg.reasoning) {\n innerContent += \\`\n <div class=\"reasoning-block\" id=\"reasoning-\\${msgId}\">\n <div class=\"reasoning-header\" onclick=\"toggleReasoning('reasoning-\\${msgId}')\">\n <svg class=\"reasoning-icon\" viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" fill=\"currentColor\"><path d=\"M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z\"></path></svg>\n <span>\\${escapeHtml(t('thinking_process'))}</span>\n </div>\n <div class=\"reasoning-content markdown-body\">\n \\${marked.parse(msg.reasoning)}\n </div>\n </div>\n \\`;\n }\n\n // 检测子代理完成消息,使用 tool-call-container 风格渲染(类似 glob)\n const agentCompletePattern = /^[\\\\s\\\\S]*\\\\[子代理\\\\s+(\\\\S+)\\\\s+执行完成\\\\]:[\\\\s\\\\S]*$/;\n const agentCompleteMatch = msg.content.match(agentCompletePattern);\n if (agentCompleteMatch) {\n const agentName = agentCompleteMatch[1];\n // 查找子代理对应的 agentId(使用前端的 allAgents 数组)\n const subAgent = allAgents.find(a => a.name === agentName);\n const subAgentId = subAgent ? subAgent.id : null;\n const clickAttr = subAgentId ? \\`onclick=\"switchAgent('\\${subAgentId}')\"\\` : '';\n const linkHtml = subAgentId\n ? \\`<div style=\"font-size:11px; color:var(--text-secondary); margin-left:4px; cursor:pointer;\" \\${clickAttr}>\\${escapeHtml(t('subagent_view_messages'))}</div>\\`\n : '';\n\n innerContent += \\`\n <div class=\"tool-call-container\">\n <div class=\"tool-header\">\n <span class=\"tool-header-name\">\\${escapeHtml(t('subagent_done'))}</span>\n </div>\n <div class=\"tool-content\">\n <div class=\"bash-command\">【\\${escapeHtml(agentName)}】\\${escapeHtml(t('subagent_done'))}</div>\n \\${linkHtml}\n </div>\n </div>\n \\`;\n } else if (msg.content.startsWith('[Error:') || msg.content.startsWith('[API Error:')) {\n // 错误消息使用红色样式\n innerContent += \\`<div class=\"tool-error\">\\${escapeHtml(msg.content)}</div>\\`;\n } else {\n innerContent += \\`<div class=\"markdown-body\">\\${marked.parse(msg.content)}</div>\\`;\n }\n\n if (msg.toolCalls && msg.toolCalls.length > 0) {\n const toolsHtml = msg.toolCalls.map(call => {\n const displayName = getToolDisplayName(call.name);\n const template = getToolRenderTemplate(call.name);\n let innerHtml;\n\n if (template.call) {\n innerHtml = applyTemplate(template.call, call.arguments);\n } else {\n innerHtml = \\`<pre style=\"margin:0; font-size:12px;\">\\${JSON.stringify(call.arguments, null, 2)}</pre>\\`;\n }\n\n return \\`\n <div class=\"tool-call-container\">\n <div class=\"tool-header\">\n <span class=\"tool-header-name\">\\${displayName}</span>\n </div>\n <div class=\"tool-content\">\\${innerHtml}</div>\n </div>\n \\`;\n }).join('');\n innerContent += toolsHtml;\n }\n\n contentHtml = \\`<div class=\"message-content\" id=\"\\${msgId}\">\\${innerContent}</div>\\`;\n\n } else if (role === 'tool') {\n const toolCallId = msg.toolCallId;\n let toolName = null;\n let toolArgs = {};\n\n // 查找对应的工具调用(需要传入完整消息列表)\n return ''; // 这个需要在完整上下文中处理,暂时返回空\n }\n\n return \\`\n <div class=\"message-row \\${role}\">\n <div class=\"message-meta\">\n \\${metaHtml}\n </div>\n \\${contentHtml}\n </div>\n \\`;\n }\n\n // 追加新消息(保持现有 DOM 状态)\n function appendNewMessages(newMessages, startIndex) {\n // 移除空状态\n const emptyState = container.querySelector('.empty-state');\n if (emptyState) emptyState.remove();\n\n // 获取当前消息数量\n const currentCount = container.querySelectorAll('.message-row').length;\n\n newMessages.forEach((msg, i) => {\n const index = startIndex + i;\n const msgId = \\`msg-\\${index}\\`;\n let html = '';\n\n if (msg.role === 'user' || msg.role === 'system' || msg.role === 'assistant') {\n html = renderMessage(msg, index);\n } else if (msg.role === 'tool') {\n // tool 需要特殊处理,查找对应的 toolCall\n let toolName = null;\n let toolArgs = {};\n const messages = currentMessages;\n const toolCallId = msg.toolCallId;\n\n for (const m of messages) {\n if (m.toolCalls) {\n const found = m.toolCalls.find(c => c.id === toolCallId);\n if (found) {\n toolName = found.name;\n toolArgs = found.arguments;\n break;\n }\n }\n }\n\n const { success, data } = parseToolResult(msg.content);\n const displayName = getToolDisplayName(toolName);\n const template = getToolRenderTemplate(toolName);\n\n let bodyHtml;\n if (template.result) {\n bodyHtml = applyTemplate(template.result, data, success, toolArgs);\n } else {\n const displayData = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;\n bodyHtml = \\`<pre class=\"bash-output\">\\${displayData}</pre>\\`;\n }\n\n html = \\`\n <div class=\"message-row \\${msg.role}\">\n <div class=\"message-meta\">\n <div class=\"role-badge\">\\${msg.role}</div>\n </div>\n <div class=\"message-content\" id=\"\\${msgId}\" style=\"padding:0; overflow:hidden;\">\n <div class=\"tool-result-header\">\n <span class=\"status-dot \\${success ? 'success' : 'error'}\"></span>\n <span>\\${displayName}</span>\n </div>\n <div class=\"tool-result-body\">\\${bodyHtml}</div>\n </div>\n </div>\n \\`;\n }\n\n // 追加到容器\n container.insertAdjacentHTML('beforeend', html);\n });\n\n // 对新消息应用折叠逻辑\n applyCollapseLogic(container, startIndex);\n updateRollbackActionVisibility();\n updateFollowLatestButton();\n if (followLatestEnabled) {\n scheduleScrollToLatest('smooth');\n }\n }\n\n // 更新最后一条消息\n function updateLastMessage(msg) {\n const lastIndex = currentMessages.length - 1;\n const lastRow = container.querySelectorAll('.message-row')[lastIndex];\n if (!lastRow) {\n render(currentMessages);\n return;\n }\n\n const msgId = \\`msg-\\${lastIndex}\\`;\n\n if (msg.role === 'tool') {\n // tool 消息更新:重建 tool-result-body\n const toolCallId = msg.toolCallId;\n let toolName = null;\n let toolArgs = {};\n\n for (const m of currentMessages) {\n if (m.toolCalls) {\n const found = m.toolCalls.find(c => c.id === toolCallId);\n if (found) {\n toolName = found.name;\n toolArgs = found.arguments;\n break;\n }\n }\n }\n\n const { success, data } = parseToolResult(msg.content);\n const displayName = getToolDisplayName(toolName);\n const template = getToolRenderTemplate(toolName);\n\n let bodyHtml;\n if (template.result) {\n bodyHtml = applyTemplate(template.result, data, success, toolArgs);\n } else {\n const displayData = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;\n bodyHtml = \\`<pre class=\"bash-output\">\\${displayData}</pre>\\`;\n }\n\n const toolResultBody = lastRow.querySelector('.tool-result-body');\n if (toolResultBody) {\n toolResultBody.innerHTML = bodyHtml;\n }\n }\n\n updateRollbackActionVisibility();\n updateFollowLatestButton();\n if (followLatestEnabled) {\n scheduleScrollToLatest('smooth');\n }\n }\n\n // 应用折叠逻辑(只处理指定索引后的消息)\n function applyCollapseLogic(containerElement, startIndex = 0) {\n const rows = containerElement.querySelectorAll('.message-row');\n rows.forEach((row, idx) => {\n if (idx < startIndex) return; // 跳过旧消息\n\n const el = row.querySelector('.message-content');\n if (!el) return;\n\n const isCollapsible = el.scrollHeight > 160;\n const isSystem = row.classList.contains('system');\n const toolName = row.querySelector('.tool-result-header span:last-child')?.textContent || '';\n const isReadOrEdit = toolName === 'Read' || toolName === 'Edit';\n const shouldCollapse = isCollapsible && (isSystem || isReadOrEdit);\n\n if (isCollapsible) {\n if (shouldCollapse) {\n el.classList.add('collapsed');\n const meta = row.querySelector('.message-meta .collapse-toggle svg');\n if (meta) meta.style.transform = 'rotate(-90deg)';\n } else {\n el.classList.remove('collapsed');\n const meta = row.querySelector('.message-meta .collapse-toggle svg');\n if (meta) meta.style.transform = 'rotate(0deg)';\n }\n\n let btnBar = row.querySelector('.expand-toggle-bar');\n if (!btnBar) {\n btnBar = document.createElement('div');\n btnBar.className = 'expand-toggle-bar';\n row.appendChild(btnBar);\n }\n\n const isCollapsed = el.classList.contains('collapsed');\n btnBar.innerHTML = '<button class=\"expand-toggle-btn\" onclick=\"toggleMessage(&quot;' + el.id + '&quot;)\">' + getToggleButtonLabel(isCollapsed) + '</button>';\n\n } else {\n const toggle = row.querySelector('.collapse-toggle');\n if (toggle) toggle.style.display = 'none';\n }\n });\n }\n\n function render(messages) {\n if (messages.length === 0) {\n container.innerHTML = getEmptyStateHtml();\n updateFollowLatestButton();\n return;\n }\n\n const html = messages.map((msg, index) => {\n const role = msg.role;\n const msgId = \\`msg-\\${index}\\`;\n let contentHtml = '';\n let metaHtml = \\`<div class=\"role-badge\">\\${role}</div>\\`;\n if (canRollbackMessage(msg)) {\n metaHtml += \\`<button class=\"message-action\" onclick=\"requestRollbackEdit(\\${index})\">编辑此轮</button>\\`;\n }\n\n if (role === 'user' || role === 'system') {\n let style = '';\n let rowClass = role;\n if (role === 'system') {\n const isLong = msg.content.includes('\\\\n') || msg.content.length > 60;\n if (isLong) {\n style = 'text-align: left !important;';\n rowClass += ' long-content';\n }\n contentHtml = \\`<div class=\"message-content markdown-body\" id=\"\\${msgId}\" style=\"\\${style}\">\\${marked.parse(msg.content)}</div>\\`;\n } else {\n contentHtml = \\`<div class=\"message-content markdown-body\" id=\"\\${msgId}\">\\${marked.parse(msg.content)}</div>\\`;\n }\n \n if (role === 'system') {\n return \\`\n <div class=\"message-row \\${rowClass}\">\n <div class=\"message-meta\">\n \\${metaHtml}\n </div>\n \\${contentHtml}\n </div>\n \\`;\n }\n } else if (role === 'assistant') {\n let innerContent = '';\n\n if (msg.reasoning) {\n innerContent += \\`\n <div class=\"reasoning-block\" id=\"reasoning-\\${msgId}\">\n <div class=\"reasoning-header\" onclick=\"toggleReasoning('reasoning-\\${msgId}')\">\n <svg class=\"reasoning-icon\" viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" fill=\"currentColor\"><path d=\"M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z\"></path></svg>\n <span>\\${escapeHtml(t('thinking_process'))}</span>\n </div>\n <div class=\"reasoning-content markdown-body\">\n \\${marked.parse(msg.reasoning)}\n </div>\n </div>\n \\`;\n }\n\n // 检测子代理完成消息,使用 tool-call-container 风格渲染(类似 glob)\n const agentCompletePattern = /^[\\\\s\\\\S]*\\\\[子代理\\\\s+(\\\\S+)\\\\s+执行完成\\\\]:[\\\\s\\\\S]*$/;\n const agentCompleteMatch = msg.content.match(agentCompletePattern);\n if (agentCompleteMatch) {\n const agentName = agentCompleteMatch[1];\n // 查找子代理对应的 agentId(使用前端的 allAgents 数组)\n const subAgent = allAgents.find(a => a.name === agentName);\n const subAgentId = subAgent ? subAgent.id : null;\n const clickAttr = subAgentId ? \\`onclick=\"switchAgent('\\${subAgentId}')\"\\` : '';\n const linkHtml = subAgentId\n ? \\`<div style=\"font-size:11px; color:var(--text-secondary); margin-left:4px; cursor:pointer;\" \\${clickAttr}>\\${escapeHtml(t('subagent_view_messages'))}</div>\\`\n : '';\n\n innerContent += \\`\n <div class=\"tool-call-container\">\n <div class=\"tool-header\">\n <span class=\"tool-header-name\">\\${escapeHtml(t('subagent'))}</span>\n </div>\n <div class=\"tool-content\">\n <div class=\"bash-command\">\\${escapeHtml(agentName)} \\${escapeHtml(t('subagent_done'))}</div>\n \\${linkHtml}\n </div>\n </div>\n \\`;\n } else {\n innerContent += \\`<div class=\"markdown-body\">\\${marked.parse(msg.content)}</div>\\`;\n }\n\n if (msg.toolCalls && msg.toolCalls.length > 0) {\n const toolsHtml = msg.toolCalls.map(call => {\n const displayName = getToolDisplayName(call.name);\n const template = getToolRenderTemplate(call.name);\n let innerHtml;\n\n if (template.call) {\n innerHtml = applyTemplate(template.call, call.arguments);\n } else {\n innerHtml = \\`<pre style=\"margin:0; font-size:12px;\">\\${JSON.stringify(call.arguments, null, 2)}</pre>\\`;\n }\n\n return \\`\n <div class=\"tool-call-container\">\n <div class=\"tool-header\">\n <span class=\"tool-header-name\">\\${displayName}</span>\n </div>\n <div class=\"tool-content\">\\${innerHtml}</div>\n </div>\n \\`;\n }).join('');\n innerContent += toolsHtml;\n }\n\n contentHtml = \\`<div class=\"message-content\" id=\"\\${msgId}\">\\${innerContent}</div>\\`;\n\n } else if (role === 'tool') {\n const toolCallId = msg.toolCallId;\n let toolName = null;\n let toolArgs = {};\n \n for (const m of messages) {\n if (m.toolCalls) {\n const found = m.toolCalls.find(c => c.id === toolCallId);\n if (found) { \n toolName = found.name;\n toolArgs = found.arguments;\n break; \n }\n }\n }\n\n const { success, data } = parseToolResult(msg.content);\n const displayName = getToolDisplayName(toolName);\n const template = getToolRenderTemplate(toolName);\n \n let bodyHtml;\n if (template.result) {\n bodyHtml = applyTemplate(template.result, data, success, toolArgs);\n } else {\n const displayData = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;\n bodyHtml = \\`<pre class=\"bash-output\">\\${displayData}</pre>\\`;\n }\n\n contentHtml = \\`\n <div class=\"message-content\" id=\"\\${msgId}\" style=\"padding:0; overflow:hidden;\">\n <div class=\"tool-result-header\">\n <span class=\"status-dot \\${success ? 'success' : 'error'}\"></span>\n <span>\\${displayName}</span>\n </div>\n <div class=\"tool-result-body\">\\${bodyHtml}</div>\n </div>\\`;\n }\n\n return \\`\n <div class=\"message-row \\${role}\">\n <div class=\"message-meta\">\n \\${metaHtml}\n </div>\n \\${contentHtml}\n </div>\n \\`;\n }).join('');\n\n container.innerHTML = html;\n\n document.querySelectorAll('.message-row').forEach(row => {\n const el = row.querySelector('.message-content');\n if (!el) return;\n\n const isCollapsible = el.scrollHeight > 160;\n const isSystem = row.classList.contains('system');\n // 检查是否是 read 或 edit 工具\n const toolName = row.querySelector('.tool-result-header span:last-child')?.textContent || '';\n const isReadOrEdit = toolName === 'Read' || toolName === 'Edit';\n const shouldCollapse = isCollapsible && (isSystem || isReadOrEdit);\n\n if (isCollapsible) {\n if (shouldCollapse) {\n el.classList.add('collapsed');\n // Meta toggle rotation\n const meta = row.querySelector('.message-meta .collapse-toggle svg');\n if (meta) meta.style.transform = 'rotate(-90deg)';\n } else {\n el.classList.remove('collapsed');\n const meta = row.querySelector('.message-meta .collapse-toggle svg');\n if (meta) meta.style.transform = 'rotate(0deg)';\n }\n \n // Inject Toggle Button\n let btnBar = row.querySelector('.expand-toggle-bar');\n if (!btnBar) {\n btnBar = document.createElement('div');\n btnBar.className = 'expand-toggle-bar';\n row.appendChild(btnBar);\n }\n \n const isCollapsed = el.classList.contains('collapsed');\n btnBar.innerHTML = '<button class=\"expand-toggle-btn\" onclick=\"toggleMessage(&quot;' + el.id + '&quot;)\">' + getToggleButtonLabel(isCollapsed) + '</button>';\n \n } else {\n const toggle = row.querySelector('.collapse-toggle');\n if (toggle) toggle.style.display = 'none';\n }\n });\n\n updateRollbackActionVisibility();\n updateFollowLatestButton();\n if (followLatestEnabled) {\n scheduleScrollToLatest('auto');\n }\n }\n\n window.toggleMessage = function(id) {\n const el = document.getElementById(id);\n if (el) {\n el.classList.toggle('collapsed');\n const row = el.closest('.message-row');\n const isCollapsed = el.classList.contains('collapsed');\n\n // Update meta icon\n const meta = row.querySelector('.message-meta .collapse-toggle svg');\n if (meta) {\n meta.style.transform = isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)'; // meta uses transform\n // Fix: meta.transform in previous code was wrong, it's meta.style.transform\n }\n \n // Update bottom button\n const btn = row.querySelector('.expand-toggle-btn');\n if (btn) {\n btn.innerHTML = getToggleButtonLabel(isCollapsed);\n }\n }\n };\n\n window.toggleReasoning = function(id) {\n const el = document.getElementById(id);\n if (el) {\n el.classList.toggle('expanded');\n }\n };\n\n // 初始化:先加载 Feature 模板映射,再启动轮询\n // 如果 Feature 模板映射为空(Agent 还未注册),在 loadAgents 后重新加载\n applyTheme(currentTheme);\n applyLanguage();\n\n loadFeatureTemplateMap().then((success) => {\n loadAgents().then(async () => {\n // 如果第一次加载 Feature 模板失败,重新尝试\n if (!success) {\n console.log('[Viewer] Retrying to load feature templates after agent loaded...');\n await reloadFeatureTemplateMap();\n }\n await loadMcpInfo(false);\n poll();\n });\n });\n </script>\n</body>\n</html>`;\n}\n\n/**\n * 备选方案:如果未来想改为静态 HTML 文件 + 动态变量注入\n * 可以使用此函数读取 HTML 文件并替换其中的占位符\n */\nexport async function loadViewerHtml(\n htmlPath: string,\n replacements: Record<string, string> = {}\n): Promise<string> {\n // 未来如果改用静态 HTML 文件,可以在这里实现\n // const content = await fs.readFile(htmlPath, 'utf-8');\n // let result = content;\n // for (const [key, value] of Object.entries(replacements)) {\n // result = result.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n // }\n // return result;\n throw new Error('Static HTML loading not implemented yet');\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0iMT;;;AHzhMA,IAAMA,sCAAqC;AAI3C,IAAM,eAAN,MAAmB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","names":["QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT"]}