@agentdevjs/viewer 0.1.0
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.
- package/dist/chunk-OPBQEOJC.js +8568 -0
- package/dist/chunk-OPBQEOJC.js.map +1 -0
- package/dist/cli/server.d.ts +1 -0
- package/dist/cli/server.js +43 -0
- package/dist/cli/server.js.map +1 -0
- package/dist/cli/viewer.d.ts +1 -0
- package/dist/cli/viewer.js +42 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/index.d.ts +330 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/debugger-mcp.ts","../src/viewer-html/css.ts","../src/viewer-html/body.ts","../src/viewer-html/js-state.ts","../src/viewer-html/js-i18n.ts","../src/viewer-html/js-utils.ts","../src/viewer-html/js-inspector.ts","../src/viewer-html/js-logs.ts","../src/viewer-html/js-mcp.ts","../src/viewer-html/js-lifecycle-docs.ts","../src/viewer-html/js-panels.ts","../src/viewer-html/js-ui-base.ts","../src/viewer-html/js-templates.ts","../src/viewer-html/js-agents.ts","../src/viewer-html/js-poll.ts","../src/viewer-html/js-notifications.ts","../src/viewer-html/js-choice-input.ts","../src/viewer-html/js-messages.ts","../src/viewer-html/index.ts","../src/viewer-worker.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'http';\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/server';\nimport { NodeStreamableHTTPServerTransport as StreamableHTTPServerTransport } from '@modelcontextprotocol/node';\nimport * as z from 'zod/v4';\nimport type { AgentLogsResponse, AgentSession, DebugLogEntry, HookInspectorSnapshot } from '@agentdevjs/core';\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 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 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_agent',\n description: 'Get a single agent by id. Supports \"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 logs. Prefer adding filters to narrow results; unfiltered queries are auto-truncated.',\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/{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_agent', {\n title: 'Get Agent',\n description: 'Get a single agent by id. Supports \"self\".',\n inputSchema: z.object({\n agentId: z.string().min(1).describe('Explicit Agent ID or \"self\" (resolves to the caller agent).'),\n callerAgentId: z.string().optional().describe('Optional caller agent id used to resolve \"self\".'),\n }),\n }, async ({ agentId, callerAgentId }) => {\n const resolvedAgentId = this.resolveAgentRef(agentId, callerAgentId, { required: true });\n const agent = this.dataSource.getAgent(resolvedAgentId);\n return createTextResult(jsonText({\n requestedAgentId: agentId ?? null,\n resolvedAgentId,\n agent: agent || null,\n }), {\n requestedAgentId: agentId ?? null,\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().min(1).describe('Explicit Agent ID or \"self\" (resolves to the caller agent).'),\n callerAgentId: z.string().optional().describe('Optional caller agent id used to resolve \"self\".'),\n }),\n }, async ({ agentId, callerAgentId }) => {\n const resolvedAgentId = this.resolveAgentRef(agentId, callerAgentId, { required: true });\n const hooks = this.dataSource.getHooks(resolvedAgentId);\n return createTextResult(jsonText({\n requestedAgentId: agentId ?? null,\n resolvedAgentId,\n hooks: hooks || { lifecycleOrder: [], features: [], hooks: [] },\n }), {\n requestedAgentId: agentId ?? null,\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. Use filters to narrow results; unfiltered queries are auto-truncated with guidance to refine.',\n inputSchema: z.object({\n agentId: z.string().min(1).optional().describe('Explicit Agent ID or \"self\" (resolves to the caller agent). Required with scope=current; omit only with scope=all.'),\n callerAgentId: z.string().optional().describe('Optional caller agent id used to resolve \"self\".'),\n scope: z.enum(['current', 'all']).optional().describe('Scope: \"current\" (requires agentId) or \"all\".'),\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(`Max logs to return (1-500). Defaults to ${QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT} on unfiltered queries.`),\n offset: z.number().int().min(0).optional().describe('Pagination offset. Use with limit to continue paged results.'),\n search: z.string().optional().describe('Substring search over the log message and serialized JSON payload/context.'),\n }),\n }, async (args) => {\n const scope = args.scope === 'all' ? 'all' : 'current';\n const resolvedAgentId = scope === 'current'\n ? this.resolveAgentRef(args.agentId, args.callerAgentId, { required: true })\n : this.resolveAgentRef(args.agentId, args.callerAgentId);\n const queryAgentId = resolvedAgentId || 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 ?? null,\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(\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().min(1).describe('Explicit Agent ID. Use list_agents to discover IDs.'),\n },\n }, async ({ agentId }) => {\n const resolvedAgentId = this.resolvePromptAgent(agentId);\n const agent = this.dataSource.getAgent(resolvedAgentId);\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 || 'none'}`,\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().min(1).describe('Explicit Agent ID. Use list_agents to discover IDs.'),\n },\n }, async ({ agentId }) => {\n const resolvedAgentId = this.resolvePromptAgent(agentId);\n const hooks = this.dataSource.getHooks(resolvedAgentId);\n const agent = this.dataSource.getAgent(resolvedAgentId);\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 || 'none'}`,\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().min(1).describe('Explicit Agent ID. Use list_agents to discover IDs.'),\n },\n }, async ({ agentId }) => {\n const resolvedAgentId = this.resolvePromptAgent(agentId);\n const agent = this.dataSource.getAgent(resolvedAgentId);\n const hooks = this.dataSource.getHooks(resolvedAgentId);\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 || 'none'}`,\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 /**\n * 解析 agent 引用。'self' 解析为 callerAgentId;'current' 伪 ID 已随服务端\n * current 语义移除,显式报错引导调用方传显式 ID;缺省返回 null(由调用方决定\n * 如何呈现\"未指定\")。\n */\n private resolvePromptAgent(agentId: string | undefined): string {\n return this.resolveAgentRef(agentId, undefined, { required: true });\n }\n\n private resolveAgentRef(\n requestedAgentId: string | undefined,\n callerAgentId: string | undefined,\n options: { required: true },\n ): string;\n\n private resolveAgentRef(\n requestedAgentId: string | undefined,\n callerAgentId: string | undefined,\n options?: { required?: false },\n ): string | null;\n\n private resolveAgentRef(\n requestedAgentId: string | undefined,\n callerAgentId: string | undefined,\n { required = false }: { required?: boolean } = {},\n ): string | null {\n if (requestedAgentId === 'current') {\n throw new Error(\"the 'current' pseudo-id was removed; pass an explicit agentId or 'self'\");\n }\n\n const resolved = requestedAgentId === 'self'\n ? callerAgentId || null\n : requestedAgentId?.trim() || null;\n if (required && !resolved) {\n throw new Error('an explicit agentId is required for this runtime-scoped operation');\n }\n return resolved;\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\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 return {\n ...summary,\n projectRoot: session.projectRoot,\n currentStateType: session.currentState?.type || null,\n pendingInputCount: session.inputLease ? 1 : 0,\n hookInspector: session.hookInspector,\n };\n}\n\nexport function filterDebuggerLogs(\n logs: DebugLogEntry[],\n query: Omit<DebuggerLogQuery, 'scope'> & { 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","export const VIEWER_CSS = ` <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`;\n","export const VIEWER_BODY = `<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`;\n","export const VIEWER_JS_STATE = ` // Feature 模板映射(从 API 动态加载)\r\n let FEATURE_TEMPLATE_MAP = {};\r\n\r\n // 加载 Feature 模板映射\r\n async function loadFeatureTemplateMap() {\r\n if (!currentAgentId) {\r\n return false;\r\n }\r\n try {\r\n const response = await fetch('/api/templates/feature?agentId=' + encodeURIComponent(currentAgentId));\r\n if (response.ok) {\r\n const data = await response.json();\r\n if (Object.keys(data).length > 0) {\r\n FEATURE_TEMPLATE_MAP = data;\r\n return true;\r\n }\r\n }\r\n return false;\r\n } catch (e) {\r\n console.warn('[Viewer] Failed to load feature templates:', e);\r\n return false;\r\n }\r\n }\r\n\r\n // 重新加载 Feature 模板映射\r\n async function reloadFeatureTemplateMap() {\r\n console.log('[Viewer] Reloading feature templates...');\r\n const success = await loadFeatureTemplateMap();\r\n if (success) {\r\n // 重新加载当前页面的工具配置\r\n if (currentAgentId) {\r\n await loadAgentTools(currentAgentId);\r\n // 重新渲染当前消息\r\n if (currentMessages.length > 0) {\r\n render(currentMessages);\r\n }\r\n }\r\n }\r\n }\r\n\r\n const container = document.getElementById('chat-container');\r\n const statusBadge = document.getElementById('connection-status');\r\n const agentList = document.getElementById('agent-list');\r\n const currentAgentTitle = document.getElementById('current-agent-name');\r\n const sidebar = document.getElementById('sidebar');\r\n const sidebarToggle = document.getElementById('sidebar-toggle');\r\n const featurePanel = document.getElementById('feature-panel');\r\n const featurePanelTitle = document.getElementById('feature-panel-title');\r\n const featurePanelBody = document.getElementById('feature-panel-body');\r\n const featurePanelResizer = document.getElementById('feature-panel-resizer');\r\n const agentContextMenu = document.getElementById('agent-context-menu');\r\n const deleteAgentAction = document.getElementById('delete-agent-action');\r\n const followLatestButton = document.getElementById('follow-latest-btn');\r\n const railButtons = Array.from(document.querySelectorAll('.rail-button'));\r\n const languageToggle = document.getElementById('language-toggle');\r\n const themeToggle = document.getElementById('theme-toggle');\r\n\r\n let currentAgentId = null;\r\n let allAgents = [];\r\n let currentMessages = [];\r\n let currentInputRequests = [];\r\n let choiceInputState = {};\r\n let toolRenderConfigs = {};\r\n let TOOL_NAMES = {};\r\n let contextMenuAgentId = null;\r\n let activeFeaturePanel = null;\r\n let featurePanelWidth = 320;\r\n let currentTheme = localStorage.getItem('agentdev-theme') || 'dark';\r\n let currentLanguage = localStorage.getItem('agentdev-language') || 'zh';\r\n let currentHookInspector = { lifecycleOrder: [], features: [], hooks: [] };\r\n let currentHookInspectorSignature = '';\r\n let currentOverviewSnapshot = getEmptyOverviewSnapshot();\r\n let currentOverviewSignature = '';\r\n let currentLogs = [];\r\n let currentLogsSignature = '';\r\n let currentMcpInfo = null;\r\n let logPanelScope = 'current';\r\n let logFilters = {\r\n search: '',\r\n level: 'all',\r\n feature: 'all',\r\n lifecycle: 'all',\r\n };\r\n let selectedOverviewLifecycle = 'StepFinish';\r\n let selectedFeatureName = null;\r\n let followLatestEnabled = true;\r\n let suppressFollowScrollEvent = false;\r\n let pendingFollowToBottom = false;\r\n let lastManualScrollIntentAt = 0;\r\n let followScrollSettleToken = 0;\r\n\r\n`;\r\n","export const VIEWER_JS_I18N = ` 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","export const VIEWER_JS_UTILS = `\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`;\n","export const VIEWER_JS_INSPECTOR = ` 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 'ToolResultTransform',\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 // 空桶 kind 推导(三原语命名,与实际条目的声明 kind 对齐)\n return {\n lifecycle,\n kind: lifecycle === 'ToolUse' || lifecycle === 'StepFinish'\n ? 'guard'\n : lifecycle === 'ToolResultTransform' ? 'transform' : 'observe',\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`;\n","export const VIEWER_JS_LOGS = ` 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("current")\">' + escapeHtml(t('logs_scope_current')) + '</button>',\n '<button type=\"button\" class=\"log-chip' + (logPanelScope === 'all' ? ' active' : '') + '\" onclick=\"window.setLogPanelScope("all")\">' + 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("search", 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("level", 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("feature", 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("lifecycle", 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`;\n","export const VIEWER_JS_MCP = ` 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`;\n","export const VIEWER_JS_LIFECYCLE_DOCS = ` const lifecycleDocs = {\r\n AgentInitiate: {\r\n title: { zh: 'Agent 初始化阶段', en: 'Agent initialization phase' },\r\n body: {\r\n zh: [\r\n '这个时机只会在 agent 第一次真正进入工作状态时触发一次,适合做长生命周期资源的准备工作,比如启动后台服务、建立连接、预热缓存,或者把框架级能力挂进运行环境。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' boot: { lifecycle: \"AgentInitiate\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async boot(ctx) {',\r\n ' await this.indexWorkspace();',\r\n ' await this.startObserver();',\r\n ' }',\r\n '~~~',\r\n '',\r\n '如果某个 feature 要在整个会话期间维持状态,这里通常是它最稳妥的切入点。相比 CallStart,它不会被每次用户输入重复触发。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' boot: { lifecycle: \"AgentInitiate\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async boot(ctx) {',\r\n ' await this.indexWorkspace();',\r\n ' await this.startObserver();',\r\n ' }',\r\n '~~~',\r\n '',\r\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.',\r\n ].join('\\\\n'),\r\n },\r\n },\r\n AgentDestroy: {\r\n title: { zh: 'Agent 销毁阶段', en: 'Agent destroy phase' },\r\n body: { zh: [\r\n '这是 agent 生命周期的收尾点,用来释放外部资源、停止后台线程、断开连接,以及把调试信息或缓存安全落盘。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' cleanup: { lifecycle: \"AgentDestroy\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async cleanup() {',\r\n ' await this.workerPool.stop();',\r\n ' await this.cache.flush();',\r\n ' }',\r\n '~~~',\r\n '',\r\n '如果一个 feature 在 AgentInitiate 做了重量级初始化,就应该在这里成对地清理掉。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' cleanup: { lifecycle: \"AgentDestroy\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async cleanup() {',\r\n ' await this.workerPool.stop();',\r\n ' await this.cache.flush();',\r\n ' }',\r\n '~~~',\r\n '',\r\n 'If a feature performs heavyweight setup in AgentInitiate, it should usually tear that work down here.',\r\n ].join('\\\\n') },\r\n },\r\n CallStart: {\r\n title: { zh: 'Call 开始前', en: 'Before call start' },\r\n body: { zh: [\r\n '这个时机发生在系统提示词之后、用户输入正式写入上下文之前。它非常适合做输入重写、前置注入和会话级别的轻量整理。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' rewriteInput: { lifecycle: \"CallStart\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async rewriteInput(ctx) {',\r\n ' const raw = ctx.agent?.getUserInput() ?? ctx.input;',\r\n ' ctx.agent?.setUserInput(raw.trim());',\r\n ' }',\r\n '~~~',\r\n '',\r\n '如果你想观察 feature 如何“提前影响”一次调用,这里通常是最有解释力的节点。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' rewriteInput: { lifecycle: \"CallStart\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async rewriteInput(ctx) {',\r\n ' const raw = ctx.agent?.getUserInput() ?? ctx.input;',\r\n ' ctx.agent?.setUserInput(raw.trim());',\r\n ' }',\r\n '~~~',\r\n '',\r\n 'If you want to explain how a feature affects a call before the model sees it, this is usually the clearest node.',\r\n ].join('\\\\n') },\r\n },\r\n CallFinish: {\r\n title: { zh: 'Call 结束后', en: 'After call finish' },\r\n body: { zh: [\r\n '这是一次完整调用结束后的结算点。适合做摘要、记录、指标更新、落日志,而不适合决定下一轮 ReAct 要不要继续。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' afterCall: { lifecycle: \"CallFinish\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async afterCall(ctx) {',\r\n ' this.metrics.track(ctx.completed, ctx.steps);',\r\n ' }',\r\n '~~~',\r\n '',\r\n '它更像“回合总结”,而不是流程控制点。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' afterCall: { lifecycle: \"CallFinish\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async afterCall(ctx) {',\r\n ' this.metrics.track(ctx.completed, ctx.steps);',\r\n ' }',\r\n '~~~',\r\n '',\r\n 'It behaves more like an end-of-call summary than a flow-control decision point.',\r\n ].join('\\\\n') },\r\n },\r\n StepStart: {\r\n title: { zh: 'Step 开始前', en: 'Before step start' },\r\n body: { zh: [\r\n '每轮 ReAct 循环刚开始时都会进入这里。适合做上下文补丁、提醒注入、局部状态同步。这类钩子往往会高频出现。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' injectReminder: { lifecycle: \"StepStart\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async injectReminder(ctx) {',\r\n ' if (this.shouldRemind()) {',\r\n ' ctx.context.add({ role: \"system\", content: this.reminder });',\r\n ' }',\r\n ' }',\r\n '~~~',\r\n '',\r\n '因为它会在每一轮执行,所以调试器里把它单独看出来很重要,否则很难解释某些系统消息为什么总会出现。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' injectReminder: { lifecycle: \"StepStart\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async injectReminder(ctx) {',\r\n ' if (this.shouldRemind()) {',\r\n ' ctx.context.add({ role: \"system\", content: this.reminder });',\r\n ' }',\r\n ' }',\r\n '~~~',\r\n '',\r\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.',\r\n ].join('\\\\n') },\r\n },\r\n StepFinish: {\r\n title: { zh: 'Step 结束决策点', en: 'Step finish decision point' },\r\n body: { zh: [\r\n '这是 ReAct 循环里最关键的控制点之一。模型和工具都跑完后,feature 可以在这里决定“继续下一轮”还是“就地结束”。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' decide: { lifecycle: \"StepFinish\", kind: \"guard\", role: \"advisor\" },',\r\n ' };',\r\n '',\r\n ' async decide(ctx) {',\r\n ' if (this.hasPendingDelegates()) {',\r\n ' return Decision.Approve;',\r\n ' }',\r\n ' return Decision.Continue;',\r\n ' }',\r\n '~~~',\r\n '',\r\n '如果某个 feature 能把 agent 的循环强行维持住,通常就是在这里介入。它解释的是“为什么这轮已经看起来结束了,但系统还在继续跑”。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' decide: { lifecycle: \"StepFinish\", kind: \"guard\", role: \"advisor\" },',\r\n ' };',\r\n '',\r\n ' async decide(ctx) {',\r\n ' if (this.hasPendingDelegates()) {',\r\n ' return Decision.Approve;',\r\n ' }',\r\n ' return Decision.Continue;',\r\n ' }',\r\n '~~~',\r\n '',\r\n 'If a feature can keep the agent alive beyond what looks like a natural stopping point, it is usually intervening here.',\r\n ].join('\\\\n') },\r\n },\r\n ToolUse: {\r\n title: { zh: '工具执行前决策点', en: 'Before tool execution decision point' },\r\n body: { zh: [\r\n '这是另一个高价值观察位点。工具真正执行前,feature 可以在这里批准、拒绝或者放行。所有安全策略、危险操作拦截都很适合在这里实现。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' guard: { lifecycle: \"ToolUse\", kind: \"guard\", role: \"advisor\" },',\r\n ' };',\r\n '',\r\n ' async guard(ctx) {',\r\n ' if (ctx.call.name === \"run_shell_command\") {',\r\n ' return Decision.Deny;',\r\n ' }',\r\n ' return Decision.Continue;',\r\n ' }',\r\n '~~~',\r\n '',\r\n '调试器里只要看清楚这里挂了谁,很多“为什么工具没执行”或者“为什么执行路径被改写”就能直接定位。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' guard: { lifecycle: \"ToolUse\", kind: \"guard\", role: \"advisor\" },',\r\n ' };',\r\n '',\r\n ' async guard(ctx) {',\r\n ' if (ctx.call.name === \"run_shell_command\") {',\r\n ' return Decision.Deny;',\r\n ' }',\r\n ' return Decision.Continue;',\r\n ' }',\r\n '~~~',\r\n '',\r\n 'As soon as you can see who is attached here, many \"why did the tool not run?\" questions become much easier to answer.',\r\n ].join('\\\\n') },\r\n },\r\n ToolFinished: {\r\n title: { zh: '工具执行后通知点', en: 'After tool finished notify point' },\r\n body: { zh: [\r\n '工具已经返回结果以后,这里会收到纯通知。适合做后处理、索引、同步外部状态、记录审计信息,但不会改变刚刚那次工具调用本身的结果。',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' record: { lifecycle: \"ToolFinished\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async record(ctx) {',\r\n ' this.auditTrail.push({',\r\n ' tool: ctx.toolName,',\r\n ' duration: ctx.duration,',\r\n ' });',\r\n ' }',\r\n '~~~',\r\n '',\r\n '这类钩子更偏“旁路观察”和“后续整理”,所以通常适合完整展开给开发者查链路。',\r\n ].join('\\\\n'),\r\n en: [\r\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.',\r\n '',\r\n '~~~ts',\r\n 'class MyFeature {',\r\n ' static hooks = {',\r\n ' record: { lifecycle: \"ToolFinished\", kind: \"observe\" },',\r\n ' };',\r\n '',\r\n ' async record(ctx) {',\r\n ' this.auditTrail.push({',\r\n ' tool: ctx.toolName,',\r\n ' duration: ctx.duration,',\r\n ' });',\r\n ' }',\r\n '~~~',\r\n '',\r\n 'These hooks are more about side-channel observation and cleanup, so they are usually worth showing in full detail to developers.',\r\n ].join('\\\\n') },\r\n },\r\n };\r\n`;\r\n","export const VIEWER_JS_PANELS = `\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("' + escapeHtml(name) + '")\"><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("' + escapeHtml(feature.name) + '")\" 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","export const VIEWER_JS_UI_BASE = `\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 = { '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' };\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, display) {\n let result;\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 result = { success: json.success, data: data };\n } else {\n result = { success: true, data: content };\n }\n } catch (e) {\n result = { success: true, data: content };\n }\n // Merge display-only data (e.g. write tool's diff) that bypassed LLM injection\n if (display && typeof display === 'object') {\n result.data = typeof result.data === 'object' && result.data !== null\n ? Object.assign({}, result.data, display)\n : Object.assign({}, display);\n }\n return result;\n }\n\n`;\n","export const VIEWER_JS_TEMPLATES = ` /**\r\n * 根据模板名解析文件路径\r\n * 模板 URL 由 ViewerWorker 从注册事实生成(/tpl/{mountId}/{rel}),不透明、不含布局知识。\r\n * 未注册的模板名一律等待 FEATURE_TEMPLATE_MAP 加载,不做任何本地路径推断。\r\n */\r\n const self = this;\r\n\r\n function resolveTemplatePath(templateName) {\r\n // 1. 查找 Feature 模板(从后端注入的动态数据)\r\n if (FEATURE_TEMPLATE_MAP[templateName]) {\r\n return FEATURE_TEMPLATE_MAP[templateName];\r\n }\r\n\r\n // 2. 兜底:返回 null,让调用者等待或使用默认模板\r\n // 模板缺失本质是装配缺陷,应被看见而不是被本地猜测掩盖\r\n console.warn('[Viewer] Template \"' + templateName + '\" not found in FEATURE_TEMPLATE_MAP, waiting...');\r\n return null;\r\n }\r\n\r\n /**\r\n * 异步加载模板\r\n * 支持从 Feature 目录或系统目录加载\r\n * 如果加载失败,回退到内置模板\r\n */\r\n async function loadTemplate(templateName, retryCount = 0) {\r\n if (templateCache.has(templateName)) {\r\n return templateCache.get(templateName);\r\n }\r\n\r\n // 优先检查内置模板(json 是内置的)\r\n if (RENDER_TEMPLATES[templateName]) {\r\n templateCache.set(templateName, RENDER_TEMPLATES[templateName]);\r\n return RENDER_TEMPLATES[templateName];\r\n }\r\n\r\n try {\r\n const path = resolveTemplatePath(templateName);\r\n\r\n // 如果 path 为 null,说明 FEATURE_TEMPLATE_MAP 还未加载完成\r\n if (!path) {\r\n // 最多重试 3 次,每次等待 500ms\r\n if (retryCount < 3) {\r\n console.log('[Viewer] Waiting for FEATURE_TEMPLATE_MAP to load... (attempt ' + (retryCount + 1) + ')');\r\n await new Promise(resolve => setTimeout(resolve, 500));\r\n // 重新加载模板映射\r\n await loadFeatureTemplateMap();\r\n return loadTemplate(templateName, retryCount + 1);\r\n }\r\n console.warn('[Viewer] Template \"' + templateName + '\" not found after retries');\r\n // 回退到内置 json 模板\r\n if (RENDER_TEMPLATES['json']) {\r\n return RENDER_TEMPLATES['json'];\r\n }\r\n return null;\r\n }\r\n\r\n // 统一使用 URL 方式加载模板(URL 由服务端注册事实生成,不透明)\r\n // 失败时回退到内置 json 模板但不写入缓存,下次渲染重试真实模板\r\n const module = await import(path);\r\n\r\n // 1. 优先使用 default export(Feature 模板)\r\n let template = module.default;\r\n if (template) {\r\n templateCache.set(templateName, template);\r\n return template;\r\n }\r\n\r\n // 2. 尝试从 TEMPLATES 对象获取(系统模板)\r\n if (module.TEMPLATES && module.TEMPLATES[templateName]) {\r\n template = module.TEMPLATES[templateName];\r\n templateCache.set(templateName, template);\r\n return template;\r\n }\r\n\r\n console.warn('[Viewer Worker] 模板 \"' + templateName + '\" 在文件中未找到');\r\n return null;\r\n } catch (e) {\r\n console.warn('[Viewer Worker] 加载模板失败: ' + templateName, e);\r\n // 回退到内置 json 模板\r\n if (RENDER_TEMPLATES['json']) {\r\n return RENDER_TEMPLATES['json'];\r\n }\r\n return null;\r\n }\r\n }\r\n\r\n function getToolRenderTemplate(toolName) {\r\n const config = toolRenderConfigs[toolName];\r\n const callTemplateName = (config?.render?.call) || 'json';\r\n const resultTemplateName = (config?.render?.result) || 'json';\r\n\r\n const callIsInline = callTemplateName === '__inline__';\r\n const resultIsInline = resultTemplateName === '__inline__';\r\n\r\n let callTemplate, resultTemplate;\r\n\r\n if (callIsInline) {\r\n callTemplate = config?.render?.inlineCall;\r\n } else {\r\n // 优先从缓存读取\r\n const cached = templateCache.get(callTemplateName);\r\n callTemplate = cached?.call || RENDER_TEMPLATES['json'].call;\r\n }\r\n\r\n if (resultIsInline) {\r\n resultTemplate = config?.render?.inlineResult;\r\n } else {\r\n const cached = templateCache.get(resultTemplateName);\r\n resultTemplate = cached?.result || RENDER_TEMPLATES['json'].result;\r\n }\r\n\r\n return {\r\n call: callTemplate,\r\n result: resultTemplate,\r\n isInlineCall: callIsInline,\r\n isInlineResult: resultIsInline,\r\n };\r\n }\r\n\r\n function getToolDisplayName(toolName) {\r\n return TOOL_NAMES[toolName] || toolName;\r\n }\r\n`;\r\n","export const VIEWER_JS_AGENTS = `\r\n // 焦点自持:服务端不再有\"当前 Agent\"概念,焦点由本页持有并持久化。\r\n // 恢复优先级:活跃输入请求 > localStorage 记忆 > 第一个 connected agent。\r\n function restoreFocus(agents) {\r\n const pendingInput = agents.find(a => a.connected !== false && a.pendingInputCount > 0);\r\n if (pendingInput) return pendingInput.id;\r\n try {\r\n const remembered = localStorage.getItem('agentdev-last-focused-agent');\r\n if (remembered && agents.some(a => a.id === remembered)) return remembered;\r\n } catch (e) { /* localStorage 不可用时跳过记忆 */ }\r\n const connected = agents.find(a => a.connected !== false);\r\n if (connected) return connected.id;\r\n return agents.length > 0 ? agents[0].id : null;\r\n }\r\n\r\n function persistFocus(agentId) {\r\n try {\r\n if (agentId) localStorage.setItem('agentdev-last-focused-agent', agentId);\r\n else localStorage.removeItem('agentdev-last-focused-agent');\r\n } catch (e) { /* ignore */ }\r\n }\r\n\r\n async function loadAgents() {\r\n try {\r\n const res = await fetch('/api/agents');\r\n const data = await res.json();\r\n allAgents = data.agents || [];\r\n\r\n // 焦点为空时初始化(页面首次加载 / 全部 agent 被清空后重新出现)\r\n if (!currentAgentId && allAgents.length > 0) {\r\n const restored = restoreFocus(allAgents);\r\n if (restored) {\r\n currentAgentId = restored;\r\n setFollowLatest(true);\r\n await loadAgentData(currentAgentId);\r\n }\r\n }\r\n\r\n renderAgentList();\r\n renderFeaturePanel();\r\n } catch (e) {\r\n console.error('Failed to load agents:', e);\r\n }\r\n }\r\n\r\n function renderAgentList() {\r\n agentList.innerHTML = allAgents.map(a => {\r\n const isActive = a.id === currentAgentId;\r\n const isConnected = a.connected !== false;\r\n // Agent ID 格式:agent-{序号}-{进程PID}\r\n const parts = a.id.split('-');\r\n const agentNum = parts[1] || '?';\r\n const pid = parts[2] || '';\r\n const displayId = pid ? '#'.concat(agentNum, ' (', pid, ')') : '#'.concat(agentNum);\r\n return \\`\r\n <div\r\n class=\"agent-item \\${isActive ? 'active' : ''} \\${isConnected ? '' : 'disconnected'}\"\r\n onclick=\"switchAgent('\\${a.id}')\"\r\n oncontextmenu=\"openAgentActions(event, '\\${a.id}')\"\r\n >\r\n <div class=\"agent-name\">\\${escapeHtml(a.name)}</div>\r\n <div class=\"agent-meta\">\r\n <span class=\"agent-status\">\r\n <span class=\"agent-status-dot\"></span>\r\n <span>\\${isConnected ? escapeHtml(t('status_connected')) : escapeHtml(t('status_disconnected'))}</span>\r\n </span>\r\n · \\${displayId} · \\${a.messageCount} \\${escapeHtml(t('feature_messages'))}\r\n </div>\r\n </div>\r\n \\`;\r\n }).join('');\r\n \r\n const activeAgent = allAgents.find(a => a.id === currentAgentId);\r\n if (activeAgent) {\r\n currentAgentTitle.textContent = activeAgent.name;\r\n } else {\r\n currentAgentTitle.textContent = t('page_title');\r\n }\r\n }\r\n\r\n window.switchAgent = async (newAgentId) => {\r\n if (newAgentId === currentAgentId) return;\r\n closeAgentContextMenu();\r\n currentAgentId = newAgentId; // 焦点前端自持,直接切换\r\n persistFocus(newAgentId);\r\n setFollowLatest(true);\r\n await loadAgentData(newAgentId);\r\n renderAgentList(); // Update active state\r\n };\r\n\r\n window.openAgentActions = (event, agentId) => {\r\n event.preventDefault();\r\n const agent = allAgents.find(item => item.id === agentId);\r\n if (!agent) return;\r\n openAgentContextMenu(agentId, event.clientX, event.clientY, agent.connected === false);\r\n };\r\n\r\n deleteAgentAction.addEventListener('click', async () => {\r\n if (!contextMenuAgentId) return;\r\n\r\n const agent = allAgents.find(item => item.id === contextMenuAgentId);\r\n if (!agent || agent.connected !== false) {\r\n closeAgentContextMenu();\r\n return;\r\n }\r\n\r\n const confirmed = window.confirm(t('delete_confirm'));\r\n if (!confirmed) {\r\n closeAgentContextMenu();\r\n return;\r\n }\r\n\r\n try {\r\n const res = await fetch(\\`/api/agents/\\${contextMenuAgentId}\\`, { method: 'DELETE' });\r\n const data = await res.json().catch(() => ({}));\r\n if (!res.ok) {\r\n throw new Error(data.error || t('delete_failed_generic'));\r\n }\r\n\r\n closeAgentContextMenu();\r\n await loadAgents();\r\n\r\n // 被删的是焦点 agent:按焦点恢复算法自选下一个\r\n if (contextMenuAgentId === currentAgentId) {\r\n currentAgentId = allAgents.length > 0 ? restoreFocus(allAgents) : null;\r\n if (currentAgentId) {\r\n persistFocus(currentAgentId);\r\n await loadAgentData(currentAgentId);\r\n } else {\r\n persistFocus(null);\r\n currentMessages = [];\r\n setCurrentLogs([]);\r\n setCurrentHookInspector({ lifecycleOrder: [], features: [], hooks: [] });\r\n setCurrentOverviewSnapshot(getEmptyOverviewSnapshot());\r\n container.innerHTML = getEmptyStateHtml();\r\n setFollowLatest(true);\r\n currentAgentTitle.textContent = t('page_title');\r\n }\r\n }\r\n } catch (e) {\r\n closeAgentContextMenu();\r\n window.alert(t('delete_failed') + (e && e.message ? e.message : e));\r\n }\r\n });\r\n\r\n document.addEventListener('click', (event) => {\r\n if (!agentContextMenu.contains(event.target)) {\r\n closeAgentContextMenu();\r\n }\r\n });\r\n\r\n window.addEventListener('resize', () => {\r\n closeAgentContextMenu();\r\n featurePanelWidth = Math.max(240, Math.min(640, featurePanelWidth));\r\n if (featurePanel.classList.contains('open')) {\r\n featurePanel.style.setProperty('--feature-panel-width', featurePanelWidth + 'px');\r\n }\r\n });\r\n window.addEventListener('scroll', closeAgentContextMenu, true);\r\n container.addEventListener('wheel', markManualScrollIntent, { passive: true });\r\n container.addEventListener('touchstart', markManualScrollIntent, { passive: true });\r\n container.addEventListener('keydown', (event) => {\r\n if (['ArrowUp', 'PageUp', 'Home', ' '].includes(event.key)) {\r\n markManualScrollIntent();\r\n }\r\n });\r\n container.addEventListener('scroll', () => {\r\n if (suppressFollowScrollEvent || !followLatestEnabled) {\r\n return;\r\n }\r\n if (!isNearBottom() && hasRecentManualScrollIntent()) {\r\n setFollowLatest(false);\r\n }\r\n });\r\n followLatestButton.addEventListener('click', () => {\r\n setFollowLatest(true, { scroll: true, behavior: 'smooth' });\r\n });\r\n\r\n async function loadLogs(forceRender = false) {\r\n if (logPanelScope === 'current' && !currentAgentId) {\r\n return;\r\n }\r\n try {\r\n const params = new URLSearchParams({\r\n scope: logPanelScope,\r\n });\r\n if (currentAgentId) {\r\n params.set('agentId', currentAgentId);\r\n }\r\n\r\n const res = await fetch('/api/logs?' + params.toString());\r\n if (!res.ok) {\r\n throw new Error('Failed to fetch logs');\r\n }\r\n const data = await res.json();\r\n const nextLogs = data.logs || [];\r\n const nextSignature = JSON.stringify({\r\n count: nextLogs.length,\r\n last: nextLogs.length > 0 ? nextLogs[nextLogs.length - 1].id : null,\r\n });\r\n\r\n if (nextSignature !== currentLogsSignature) {\r\n setCurrentLogs(nextLogs);\r\n if (activeFeaturePanel === 'logs') {\r\n renderFeaturePanel();\r\n }\r\n } else if (forceRender && activeFeaturePanel === 'logs') {\r\n renderFeaturePanel();\r\n }\r\n } catch (e) {\r\n if (forceRender && activeFeaturePanel === 'logs') {\r\n setCurrentLogs([]);\r\n renderFeaturePanel();\r\n }\r\n }\r\n }\r\n\r\n async function loadMcpInfo(forceRender = false) {\r\n try {\r\n const res = await fetch('/api/mcp-info');\r\n if (!res.ok) {\r\n throw new Error('Failed to fetch MCP info');\r\n }\r\n const data = await res.json();\r\n setCurrentMcpInfo(data);\r\n if (forceRender && activeFeaturePanel === 'mcp') {\r\n renderFeaturePanel();\r\n }\r\n } catch (e) {\r\n console.error('Failed to load MCP info:', e);\r\n if (forceRender && activeFeaturePanel === 'mcp') {\r\n renderFeaturePanel();\r\n }\r\n }\r\n }\r\n\r\n`;\r\n","export const VIEWER_JS_POLL = ` 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","export const VIEWER_JS_NOTIFICATIONS = `\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`;\n","export const VIEWER_JS_CHOICE_INPUT = ` function isChoiceInputRequest(req) {\r\n return !!req && req.mode === 'choices' && Array.isArray(req.questions) && req.questions.length > 0;\r\n }\r\n\r\n function getChoiceRequestById(requestId) {\r\n return (currentInputRequests || []).find(req => req.requestId === requestId) || null;\r\n }\r\n\r\n function getChoiceState(requestId) {\r\n if (!choiceInputState[requestId]) {\r\n choiceInputState[requestId] = {\r\n questionIndex: 0,\r\n answers: [],\r\n selectedIndex: 0,\r\n selectedIndexByQuestion: {},\r\n customTextByQuestion: {},\r\n supplementTextByOption: {},\r\n collapsed: false,\r\n };\r\n }\r\n return choiceInputState[requestId];\r\n }\r\n\r\n function getChoiceOptionCount(question) {\r\n const optionCount = Array.isArray(question?.options) ? Math.min(question.options.length, 4) : 0;\r\n return optionCount + (question?.allowCustom ? 1 : 0);\r\n }\r\n\r\n function buildChoiceAnswer(req, state, questionIndex) {\r\n const question = req?.questions?.[questionIndex] || {};\r\n const options = Array.isArray(question.options) ? question.options.slice(0, 4) : [];\r\n const selectedIndex = state.selectedIndexByQuestion?.[question.id] ?? (questionIndex === state.questionIndex ? state.selectedIndex : 0);\r\n const isCustom = question.allowCustom && selectedIndex >= options.length;\r\n if (isCustom) {\r\n return {\r\n questionId: question.id,\r\n customText: (state.customTextByQuestion[question.id] || '').trim(),\r\n };\r\n }\r\n const selectedOption = options[selectedIndex];\r\n const supplementKey = question.id + ':' + (selectedOption?.id || '');\r\n const supplementText = selectedOption?.allowSupplement\r\n ? (state.supplementTextByOption?.[supplementKey] || '').trim()\r\n : undefined;\r\n return {\r\n questionId: question.id,\r\n optionId: selectedOption?.id,\r\n supplementText: supplementText || undefined,\r\n };\r\n }\r\n\r\n function rememberCurrentChoice(req, state) {\r\n const question = req?.questions?.[state.questionIndex] || {};\r\n if (!question.id) return;\r\n state.selectedIndexByQuestion[question.id] = state.selectedIndex || 0;\r\n state.answers[state.questionIndex] = buildChoiceAnswer(req, state, state.questionIndex);\r\n }\r\n\r\n function renderChoiceInputRequest(container, req) {\r\n const state = getChoiceState(req.requestId);\r\n const questions = Array.isArray(req.questions) ? req.questions : [];\r\n if (state.collapsed) {\r\n container.classList.add('choice-collapsed');\r\n const mini = document.createElement('button');\r\n mini.className = 'user-choice-mini';\r\n mini.type = 'button';\r\n mini.setAttribute('onclick', \\`expandChoiceRequest('\\${req.requestId}')\\`);\r\n mini.innerHTML = \\`\r\n <span class=\"user-choice-mini-title\">\\${escapeHtml(req.prompt || '等待你的选择')}</span>\r\n <span class=\"user-choice-mini-meta\">\\${Math.min((state.questionIndex || 0) + 1, questions.length)} / \\${questions.length}</span>\r\n \\`;\r\n container.appendChild(mini);\r\n return;\r\n }\r\n\r\n container.classList.remove('choice-collapsed');\r\n const questionIndex = Math.max(0, Math.min(state.questionIndex || 0, questions.length - 1));\r\n state.questionIndex = questionIndex;\r\n const question = questions[questionIndex] || {};\r\n const options = Array.isArray(question.options) ? question.options.slice(0, 4) : [];\r\n const hasCustom = !!question.allowCustom;\r\n const optionCount = options.length + (hasCustom ? 1 : 0);\r\n state.selectedIndex = Math.max(0, Math.min(state.selectedIndexByQuestion?.[question.id] ?? state.selectedIndex ?? 0, Math.max(0, optionCount - 1)));\r\n\r\n const card = document.createElement('div');\r\n card.className = 'user-input-card user-choice-card';\r\n card.tabIndex = 0;\r\n card.setAttribute('onkeydown', \\`handleChoiceKey(event, '\\${req.requestId}')\\`);\r\n\r\n const optionHtml = options.map((option, index) => {\r\n const isActive = index === state.selectedIndex;\r\n const supplementKey = question.id + ':' + (option.id || '');\r\n const supplementText = state.supplementTextByOption?.[supplementKey] || '';\r\n const showSupplement = isActive && option.allowSupplement;\r\n const supplementLabel = option.supplementLabel || '';\r\n const supplementPlaceholder = option.supplementPlaceholder || '';\r\n return \\`\r\n <div>\r\n <button class=\"user-choice-option \\${isActive ? 'active' : ''}\" type=\"button\" onclick=\"selectChoiceOption('\\${req.requestId}', \\${index})\">\r\n <span class=\"user-choice-key\">\\${index + 1}</span>\r\n <span>\r\n <span class=\"user-choice-label\">\\${escapeHtml(option.label || option.id || ('选项 ' + (index + 1)))}</span>\r\n \\${option.description ? \\`<span class=\"user-choice-description\">\\${escapeHtml(option.description)}</span>\\` : ''}\r\n </span>\r\n </button>\r\n <div class=\"user-choice-supplement \\${showSupplement ? 'active' : ''}\">\r\n \\${supplementLabel ? \\`<div class=\"user-choice-supplement-label\">\\${escapeHtml(supplementLabel)}</div>\\` : ''}\r\n <textarea id=\"choice-supplement-\\${req.requestId}-\\${index}\" rows=\"2\"\r\n oninput=\"updateChoiceSupplementText('\\${req.requestId}', '\\${supplementKey}', this.value); autoResize(this)\"\r\n onkeydown=\"handleChoiceCustomKey(event, '\\${req.requestId}')\"\r\n placeholder=\"\\${escapeHtml(supplementPlaceholder || '补充说明(可选)')}\">\\${escapeHtml(supplementText)}</textarea>\r\n </div>\r\n </div>\r\n \\`;\r\n }).join('');\r\n\r\n const customIndex = options.length;\r\n const customActive = hasCustom && state.selectedIndex === customIndex;\r\n const customText = state.customTextByQuestion[question.id] || '';\r\n const customHtml = hasCustom ? \\`\r\n <button class=\"user-choice-option \\${customActive ? 'active' : ''}\" type=\"button\" onclick=\"selectChoiceOption('\\${req.requestId}', \\${customIndex})\">\r\n <span class=\"user-choice-key\">\\${customIndex + 1}</span>\r\n <span>\r\n <span class=\"user-choice-label\">\\${escapeHtml(question.customLabel || '其他,我想补充')}</span>\r\n <span class=\"user-choice-description\">选择后可以直接输入想说的话</span>\r\n </span>\r\n </button>\r\n <div class=\"user-choice-custom \\${customActive ? 'active' : ''}\">\r\n <textarea id=\"choice-custom-\\${req.requestId}\" rows=\"2\"\r\n oninput=\"updateChoiceCustomText('\\${req.requestId}', this.value); autoResize(this)\"\r\n onkeydown=\"handleChoiceCustomKey(event, '\\${req.requestId}')\"\r\n placeholder=\"\\${escapeHtml(question.customPlaceholder || '输入你的补充内容')}\">\\${escapeHtml(customText)}</textarea>\r\n </div>\r\n \\` : '';\r\n\r\n card.innerHTML = \\`\r\n <div class=\"user-choice-topline\">\r\n <div class=\"user-choice-title\">\\${escapeHtml(req.prompt || '需要你做个选择')}</div>\r\n <div class=\"user-choice-progress\">\\${questionIndex + 1} / \\${questions.length}</div>\r\n <button class=\"user-choice-close\" type=\"button\" title=\"临时收起\" onclick=\"collapseChoiceRequest('\\${req.requestId}')\">×</button>\r\n </div>\r\n <div class=\"user-choice-question\">\\${escapeHtml(question.question || '')}</div>\r\n <div class=\"user-choice-options\">\r\n \\${optionHtml}\r\n \\${customHtml}\r\n </div>\r\n <div class=\"user-choice-footer\">\r\n <span>↑↓ 选项,←→ 题目,Enter 确认</span>\r\n <button class=\"user-choice-submit\" type=\"button\" onclick=\"confirmChoiceQuestion('\\${req.requestId}')\">\\${questionIndex + 1 === questions.length ? '提交' : '下一题'}</button>\r\n </div>\r\n \\`;\r\n\r\n container.appendChild(card);\r\n setTimeout(() => {\r\n const customInput = customActive ? document.getElementById(\\`choice-custom-\\${req.requestId}\\`) : null;\r\n const activeOption = options[state.selectedIndex];\r\n const supplementInput = !customActive && activeOption?.allowSupplement\r\n ? document.getElementById('choice-supplement-' + req.requestId + '-' + state.selectedIndex)\r\n : null;\r\n const target = customInput || supplementInput || card;\r\n target.focus();\r\n if (customInput || supplementInput) {\r\n const el = customInput || supplementInput;\r\n const end = el.value.length;\r\n el.setSelectionRange(end, end);\r\n autoResize(el);\r\n }\r\n }, 30);\r\n }\r\n\r\n function rerenderChoiceRequest(requestId) {\r\n renderInputRequests(currentInputRequests || []);\r\n }\r\n\r\n window.selectChoiceOption = function(requestId, optionIndex) {\r\n const req = getChoiceRequestById(requestId);\r\n const state = getChoiceState(requestId);\r\n state.selectedIndex = optionIndex;\r\n const question = req?.questions?.[state.questionIndex];\r\n if (question?.id) {\r\n state.selectedIndexByQuestion[question.id] = optionIndex;\r\n }\r\n rerenderChoiceRequest(requestId);\r\n };\r\n\r\n window.collapseChoiceRequest = function(requestId) {\r\n const state = getChoiceState(requestId);\r\n const req = getChoiceRequestById(requestId);\r\n rememberCurrentChoice(req, state);\r\n state.collapsed = true;\r\n rerenderChoiceRequest(requestId);\r\n };\r\n\r\n window.expandChoiceRequest = function(requestId) {\r\n const state = getChoiceState(requestId);\r\n state.collapsed = false;\r\n rerenderChoiceRequest(requestId);\r\n };\r\n\r\n function collapsePrimaryChoiceRequest() {\r\n const request = (currentInputRequests || []).find(isChoiceInputRequest);\r\n if (request) {\r\n window.collapseChoiceRequest(request.requestId);\r\n }\r\n }\r\n\r\n window.updateChoiceCustomText = function(requestId, value) {\r\n const req = getChoiceRequestById(requestId);\r\n const state = getChoiceState(requestId);\r\n const question = req?.questions?.[state.questionIndex];\r\n if (question?.id) {\r\n state.customTextByQuestion[question.id] = value;\r\n }\r\n };\r\n\r\n window.updateChoiceSupplementText = function(requestId, supplementKey, value) {\r\n const state = getChoiceState(requestId);\r\n if (!state.supplementTextByOption) {\r\n state.supplementTextByOption = {};\r\n }\r\n state.supplementTextByOption[supplementKey] = value;\r\n };\r\n\r\n window.handleChoiceKey = function(event, requestId) {\r\n const req = getChoiceRequestById(requestId);\r\n if (!req) return;\r\n const state = getChoiceState(requestId);\r\n const question = req.questions[state.questionIndex] || {};\r\n const optionCount = getChoiceOptionCount(question);\r\n if (event.key === 'ArrowDown') {\r\n event.preventDefault();\r\n state.selectedIndex = Math.min(optionCount - 1, (state.selectedIndex || 0) + 1);\r\n if (question.id) state.selectedIndexByQuestion[question.id] = state.selectedIndex;\r\n rerenderChoiceRequest(requestId);\r\n } else if (event.key === 'ArrowUp') {\r\n event.preventDefault();\r\n state.selectedIndex = Math.max(0, (state.selectedIndex || 0) - 1);\r\n if (question.id) state.selectedIndexByQuestion[question.id] = state.selectedIndex;\r\n rerenderChoiceRequest(requestId);\r\n } else if (event.key === 'ArrowRight') {\r\n event.preventDefault();\r\n rememberCurrentChoice(req, state);\r\n state.questionIndex = Math.min(req.questions.length - 1, state.questionIndex + 1);\r\n state.selectedIndex = state.selectedIndexByQuestion[req.questions[state.questionIndex]?.id] ?? 0;\r\n rerenderChoiceRequest(requestId);\r\n } else if (event.key === 'ArrowLeft') {\r\n event.preventDefault();\r\n rememberCurrentChoice(req, state);\r\n state.questionIndex = Math.max(0, state.questionIndex - 1);\r\n state.selectedIndex = state.selectedIndexByQuestion[req.questions[state.questionIndex]?.id] ?? 0;\r\n rerenderChoiceRequest(requestId);\r\n } else if (event.key === 'Enter') {\r\n event.preventDefault();\r\n confirmChoiceQuestion(requestId);\r\n }\r\n };\r\n\r\n window.handleChoiceCustomKey = function(event, requestId) {\r\n if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey) {\r\n event.preventDefault();\r\n confirmChoiceQuestion(requestId);\r\n }\r\n };\r\n\r\n window.confirmChoiceQuestion = async function(requestId) {\r\n const req = getChoiceRequestById(requestId);\r\n if (!req) return;\r\n const state = getChoiceState(requestId);\r\n const questions = req.questions || [];\r\n rememberCurrentChoice(req, state);\r\n\r\n // Validate supplement-required for current question\r\n const currentQuestion = questions[state.questionIndex];\r\n if (currentQuestion) {\r\n const currentAnswer = state.answers[state.questionIndex];\r\n if (currentAnswer && currentAnswer.optionId) {\r\n const selectedOption = (currentQuestion.options || []).find(o => o.id === currentAnswer.optionId);\r\n if (selectedOption && selectedOption.allowSupplement && selectedOption.supplementRequired) {\r\n const supplementKey = currentQuestion.id + ':' + currentAnswer.optionId;\r\n const supplementValue = (state.supplementTextByOption?.[supplementKey] || '').trim();\r\n if (!supplementValue) {\r\n const supplementInput = document.getElementById('choice-supplement-' + requestId + '-' + (currentQuestion.options.indexOf(selectedOption)));\r\n if (supplementInput) {\r\n supplementInput.focus();\r\n supplementInput.style.borderColor = '#dc3545';\r\n setTimeout(() => { supplementInput.style.borderColor = ''; }, 1500);\r\n }\r\n return;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (state.questionIndex < questions.length - 1) {\r\n state.questionIndex += 1;\r\n state.selectedIndex = state.selectedIndexByQuestion[questions[state.questionIndex]?.id] ?? 0;\r\n rerenderChoiceRequest(requestId);\r\n return;\r\n }\r\n\r\n const finalAnswers = questions.map((_, index) => state.answers[index] || buildChoiceAnswer(req, state, index));\r\n const summary = finalAnswers.map((item, index) => {\r\n const q = questions[index] || {};\r\n if (item.customText) return \\`\\${q.question || item.questionId}: \\${item.customText}\\`;\r\n const option = (q.options || []).find(candidate => candidate.id === item.optionId);\r\n let line = \\`\\${q.question || item.questionId}: \\${option?.label || item.optionId || ''}\\`;\r\n if (item.supplementText) line += \\` (\\${item.supplementText})\\`;\r\n return line;\r\n }).join('\\\\n');\r\n\r\n const targetAgentId = String(currentAgentId || '').trim();\r\n if (!targetAgentId) {\r\n console.error('[Viewer] Cannot submit choice input without an explicit agent target');\r\n return;\r\n }\r\n try {\r\n const res = await fetch(\\`/api/agents/\\${encodeURIComponent(targetAgentId)}/input\\`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n requestId,\r\n input: summary,\r\n response: {\r\n kind: 'choices',\r\n choices: finalAnswers,\r\n text: summary,\r\n },\r\n }),\r\n });\r\n if (res.ok) {\r\n delete choiceInputState[requestId];\r\n poll();\r\n }\r\n } catch (e) {\r\n console.error('提交选择失败:', e);\r\n }\r\n };\r\n\r\n function syncRollbackActionButtons() {\r\n const allowRollback = !!getPrimaryInputRequest();\r\n const rows = container.querySelectorAll('.message-row');\r\n\r\n rows.forEach((row, index) => {\r\n const msg = currentMessages[index];\r\n const meta = row.querySelector('.message-meta');\r\n if (!meta) return;\r\n\r\n const existingButton = meta.querySelector('.message-action');\r\n const shouldShow = allowRollback && !!msg && msg.role === 'user';\r\n\r\n if (!shouldShow) {\r\n if (existingButton) {\r\n existingButton.remove();\r\n }\r\n return;\r\n }\r\n\r\n if (existingButton) {\r\n existingButton.setAttribute('onclick', 'requestRollbackEdit(' + index + ')');\r\n existingButton.style.display = '';\r\n return;\r\n }\r\n\r\n const button = document.createElement('button');\r\n button.className = 'message-action';\r\n button.type = 'button';\r\n button.textContent = '编辑此轮';\r\n button.setAttribute('onclick', 'requestRollbackEdit(' + index + ')');\r\n meta.appendChild(button);\r\n });\r\n }\r\n\r\n function updateRollbackActionVisibility() {\r\n syncRollbackActionButtons();\r\n }\r\n\r\n function autoResize(textarea) {\r\n textarea.style.height = 'auto';\r\n textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';\r\n }\r\n\r\n function handleInputKey(event, requestId) {\r\n if (event.key === 'Enter') {\r\n if (event.ctrlKey || event.shiftKey) {\r\n // Ctrl+Enter or Shift+Enter for new line\r\n // default behavior is new line, but we might want to ensure it works\r\n return; \r\n } else {\r\n // Enter for submit\r\n event.preventDefault();\r\n submitInput(requestId);\r\n }\r\n }\r\n }\r\n\r\n // 提交输入\r\n async function submitInput(requestId) {\r\n const textarea = document.getElementById(\\`input-\\${requestId}\\`);\r\n const input = textarea ? textarea.value : '';\r\n const targetAgentId = String(currentAgentId || '').trim();\r\n if (!targetAgentId) {\r\n console.error('[Viewer] Cannot submit input without an explicit agent target');\r\n return;\r\n }\r\n\r\n try {\r\n const res = await fetch(\\`/api/agents/\\${encodeURIComponent(targetAgentId)}/input\\`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n requestId,\r\n input,\r\n response: {\r\n kind: 'text',\r\n text: input,\r\n },\r\n })\r\n });\r\n if (res.ok) {\r\n setFollowLatest(true, { scroll: true, behavior: 'smooth' });\r\n // 刷新输入请求列表\r\n poll();\r\n }\r\n } catch (e) {\r\n console.error('提交输入失败:', e);\r\n }\r\n }\r\n\r\n function getPrimaryInputRequest() {\r\n return Array.isArray(currentInputRequests) && currentInputRequests.length > 0\r\n ? currentInputRequests[0]\r\n : null;\r\n }\r\n\r\n function canRollbackMessage(msg) {\r\n return !!getPrimaryInputRequest() && !!msg && msg.role === 'user';\r\n }\r\n\r\n async function submitInputAction(requestId, actionId, payload = {}) {\r\n const targetAgentId = String(currentAgentId || '').trim();\r\n if (!targetAgentId) {\r\n console.error('[Viewer] Cannot submit input action without an explicit agent target');\r\n return;\r\n }\r\n try {\r\n const res = await fetch(\\`/api/agents/\\${encodeURIComponent(targetAgentId)}/input\\`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n requestId,\r\n input: '',\r\n response: {\r\n kind: 'action',\r\n actionId,\r\n payload,\r\n },\r\n }),\r\n });\r\n if (res.ok) {\r\n poll();\r\n }\r\n } catch (e) {\r\n console.error('提交动作失败:', e);\r\n }\r\n }\r\n\r\n window.requestRollbackEdit = async function(messageIndex) {\r\n const request = getPrimaryInputRequest();\r\n if (!request) {\r\n console.warn('No pending input request available for rollback action');\r\n return;\r\n }\r\n\r\n const msg = currentMessages[messageIndex];\r\n if (!msg || msg.role !== 'user') {\r\n return;\r\n }\r\n\r\n const fallbackCallIndex = currentMessages\r\n .slice(0, messageIndex + 1)\r\n .filter(entry => entry.role === 'user')\r\n .length - 1;\r\n const callIndex = typeof msg.turn === 'number' ? msg.turn : fallbackCallIndex;\r\n\r\n await submitInputAction(request.requestId, 'rollback_to_call', {\r\n callIndex,\r\n draftInput: msg.content,\r\n });\r\n };\r\n\r\n`;\r\n","export const VIEWER_JS_MESSAGES = ` // 生成单条消息的 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, msg.display);\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, msg.display);\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("' + el.id + '")\">' + 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, msg.display);\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("' + el.id + '")\">' + 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`;\n","/**\n * Viewer HTML Template - 调试器前端 HTML 模板(拆分版)\n *\n * 原始的单文件 viewer-html.ts 已按职责拆分为同目录下的多个模块。\n * 本文件负责将各模块按正确顺序拼接为完整的 HTML 字符串。\n */\n\nimport { VIEWER_CSS } from './css.js';\nimport { VIEWER_BODY } from './body.js';\nimport { VIEWER_JS_STATE } from './js-state.js';\nimport { VIEWER_JS_I18N } from './js-i18n.js';\nimport { VIEWER_JS_UTILS } from './js-utils.js';\nimport { VIEWER_JS_INSPECTOR } from './js-inspector.js';\nimport { VIEWER_JS_LOGS } from './js-logs.js';\nimport { VIEWER_JS_MCP } from './js-mcp.js';\nimport { VIEWER_JS_LIFECYCLE_DOCS } from './js-lifecycle-docs.js';\nimport { VIEWER_JS_PANELS } from './js-panels.js';\nimport { VIEWER_JS_UI_BASE } from './js-ui-base.js';\nimport { VIEWER_JS_TEMPLATES } from './js-templates.js';\nimport { VIEWER_JS_AGENTS } from './js-agents.js';\nimport { VIEWER_JS_POLL } from './js-poll.js';\nimport { VIEWER_JS_NOTIFICATIONS } from './js-notifications.js';\nimport { VIEWER_JS_CHOICE_INPUT } from './js-choice-input.js';\nimport { VIEWER_JS_MESSAGES } from './js-messages.js';\n\n/**\n * 生成调试器前端页面的完整 HTML\n * @param port - HTTP 服务器端口号(保留用于 API 兼容,当前未使用)\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` + VIEWER_CSS + `</head>\n` + VIEWER_BODY + ` <script>\n` + VIEWER_JS_STATE\n+ VIEWER_JS_I18N\n+ VIEWER_JS_UTILS\n+ VIEWER_JS_INSPECTOR\n+ VIEWER_JS_LOGS\n+ VIEWER_JS_MCP\n+ VIEWER_JS_LIFECYCLE_DOCS\n+ VIEWER_JS_PANELS\n+ VIEWER_JS_UI_BASE\n+ VIEWER_JS_TEMPLATES\n+ VIEWER_JS_AGENTS\n+ VIEWER_JS_POLL\n+ VIEWER_JS_NOTIFICATIONS\n+ VIEWER_JS_CHOICE_INPUT\n+ VIEWER_JS_MESSAGES + ` </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","\n/**\n * Viewer Worker - 在独立进程中运行 HTTP 服务器\n * 支持多 Agent 调试,共享单端口\n * 支持通过 UDS(Unix Domain Socket)或 Windows Named Pipe 接收来自多进程的连接\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'http';\nimport { createServer as createNetServer, type Server, type Socket } from 'net';\nimport { unlinkSync, existsSync, readFile } from 'fs';\nimport { createHash } from 'crypto';\nimport { join, resolve, sep, extname } from 'path';\nimport { type Message, type Tool, type DebugLogEntry, type AgentOverviewSnapshot, type AgentRuntimeStateSnapshot, type TodoPlanSnapshot, type TodoTaskSnapshot, type AgentSession, type DebugHubIPCMessage, type ImageInput, type InputLease, type InputRequestCancelledMsg, type QueuedInput, type UserInputResponse, type UserTurnInput, type UserTurnSubmissionResult, type ToolMetadata, getDefaultUDSPath } from '@agentdevjs/core';\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 '@agentdevjs/core';\nimport { generateViewerHtml } from './viewer-html/index.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 // 模板装载注册表(mountId → 装载点)。mountId 是 root 的稳定哈希,\n // 多 agent 共享同一 root(如同一个框架包 junction)时复用同一条目。\n private templateMounts: Map<string, { root: string; agents: Set<string> }> = new Map();\n\n // 每个 agent 的模板装载载荷(agentId → { mounts 根数组, entries 模板名→{mount,rel} })\n private templatePayloads: Map<string, { mounts: string[]; entries: Record<string, { mount: number; rel: string }> }> = new Map();\n\n private readonly debuggerMcp = new DebuggerMCPServer({\n listAgents: () => this.listAgentSummaries(),\n getAgent: (agentId: string) => this.getAgentDetails(agentId),\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.setEncoding('utf8');\n socket.on('data', (data: string) => {\n buffer += data;\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 'update-todo-plan':\n this.handleUpdateTodoPlan(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 '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 'input-request-cancelled':\n this.handleInputRequestCancelled(msg);\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 // 模板装载资产:/tpl/{mountId}/{mount内相对路径}\n // URL 层级与 mount root 下磁盘层级同构,模板内的相对依赖(tsup 共享\n // chunk、sourcemap)按相同布局自然命中,无需任何布局推断。\n if (url.startsWith('/tpl/')) {\n this.handleTplAsset(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/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/todo - 指定 Agent 的 todo/plan 快照\n const todoMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/todo$/);\n if (todoMatch && req.method === 'GET') {\n this.handleGetAgentTodoPlan(req, res, todoMatch[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/user-turn - 统一提交一个新的用户回合\n const userTurnMatch = url.match(/^\\/api\\/agents\\/([^/]+)\\/user-turn$/);\n if (userTurnMatch && req.method === 'POST') {\n this.handlePostUserTurn(req, res, userTurnMatch[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 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 inputAccepted: session.inputPolicy !== 'none',\n // 活跃输入租约标志:前端用它做焦点恢复(inputRequest 优先)\n pendingInputCount: session.inputLease ? 1 : 0,\n }));\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n agents,\n }));\n }\n\n /**\n * GET /api/templates/feature - 获取 Feature 模板映射\n */\n public handleGetFeatureTemplates(req: IncomingMessage, res: ServerResponse, searchParams?: URLSearchParams): void {\n // 目标 agentId 必须显式指定(焦点语义已前端化,服务端不再有\"当前 Agent\")\n const targetAgentId = searchParams?.get('agentId') || null;\n if (!targetAgentId) {\n res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'agentId query parameter is required' }));\n return;\n }\n\n const payload = this.templatePayloads.get(targetAgentId);\n if (!payload || Object.keys(payload.entries).length === 0) {\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end('{}');\n return;\n }\n\n // URL = /tpl/{mountId}/{rel}。mountId 由装载根目录哈希而来,与 agent 无关:\n // 多 agent 共享同一包时浏览器复用同一份模块缓存,chunk 相对 import 也天然命中。\n const featureTemplateMapForFrontend: Record<string, string> = {};\n for (const [templateName, entry] of Object.entries(payload.entries)) {\n const root = payload.mounts[entry.mount];\n if (root === undefined) continue;\n featureTemplateMapForFrontend[templateName] = `/tpl/${ViewerWorker.mountIdForRoot(root)}/${entry.rel}`;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(featureTemplateMapForFrontend));\n }\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 private handleGetAgentTodoPlan(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 res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(session.todoPlan ?? this.createEmptyTodoPlan()));\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 scope = searchParams.get('scope') === 'all' ? 'all' : 'current';\n const agentId = searchParams.get('agentId');\n if (scope === 'current' && !agentId) {\n res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n success: false,\n code: 'invalid_target',\n error: 'agentId query parameter is required for current logs',\n }));\n return;\n }\n\n const result = this.queryLogs({\n scope,\n 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 res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n success: true,\n agentId,\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 lease = session.inputLease;\n const requests = lease ? [{ ...lease }] : [];\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.setEncoding('utf8');\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 lease = session?.inputLease;\n if (!session || !lease || lease.requestId !== 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 if (!this.forwardInputResponse(agentId, session, requestId, input ?? '', normalizedResponse)) {\n res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n success: false,\n code: 'runtime_not_accepting_input',\n error: 'Agent runtime is not connected',\n }));\n return;\n }\n\n // IPC 接管成功后再移除请求,避免“HTTP 成功但消息未发送”的假成功。\n delete session.inputLease;\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 * 提交一个不绑定 requestId 的新用户回合。\n *\n * inputLease 与 queuedInputs 都由 ViewerWorker 持有,因此仲裁必须\n * 在这里原子完成,不能由宿主先 GET 再 POST 拼接,否则会产生 TOCTOU 竞态。\n */\n public submitUserTurn(agentId: string, input: UserTurnInput): UserTurnSubmissionResult {\n if (!input || typeof input.text !== 'string' || input.text.length === 0) {\n return { success: false, code: 'invalid_input', error: 'text must be a non-empty string' };\n }\n if (input.images !== undefined && !Array.isArray(input.images)) {\n return { success: false, code: 'invalid_input', error: 'images must be an array when provided' };\n }\n if (input.source !== undefined\n && (typeof input.source !== 'string' || input.source.length === 0 || input.source.length > 128)) {\n return { success: false, code: 'invalid_input', error: 'source must be a non-empty string up to 128 characters' };\n }\n if (input.sourceRef !== undefined\n && (typeof input.sourceRef !== 'string' || input.sourceRef.length === 0 || input.sourceRef.length > 512)) {\n return { success: false, code: 'invalid_input', error: 'sourceRef must be a non-empty string up to 512 characters' };\n }\n if (input.capabilityActivations !== undefined\n && (!Array.isArray(input.capabilityActivations)\n || input.capabilityActivations.length > 16\n || input.capabilityActivations.some((a) => typeof a !== 'string' || a.length === 0 || a.length > 128))) {\n return { success: false, code: 'invalid_input', error: 'capabilityActivations must be an array of up to 16 non-empty strings (max 128 chars each)' };\n }\n\n const session = this.agentSessions.get(agentId);\n if (!session) {\n return { success: false, code: 'agent_not_found', error: 'Agent not found' };\n }\n if (!this.isSessionConnected(session)) {\n return {\n success: false,\n code: 'runtime_not_accepting_input',\n error: 'Agent runtime is not connected',\n };\n }\n\n const lease = session.inputLease;\n if (lease?.mode === 'choices') {\n return {\n success: false,\n code: 'input_mode_conflict',\n error: 'A non-text interactive input request must be completed before submitting a new user turn',\n pendingMode: 'choices',\n };\n }\n\n if (lease) {\n const requestId = lease.requestId;\n const response = this.createTextInputResponse(input);\n if (!this.forwardInputResponse(agentId, session, requestId, input.text, response)) {\n return {\n success: false,\n code: 'runtime_not_accepting_input',\n error: 'Agent runtime is not connected',\n };\n }\n delete session.inputLease;\n return {\n success: true,\n delivery: 'input',\n requestId,\n ...(input.source ? { source: input.source } : {}),\n ...(input.sourceRef ? { sourceRef: input.sourceRef } : {}),\n };\n }\n\n // 这是 runtime 的会话邮箱,不依赖 callActive。它承接新建/恢复会话中\n // “前端已可编辑、输入循环尚未开放 lease”这一正常启动阶段的首条消息。\n // 邮箱封禁(inputPolicy 'none')只拒绝排队注入;feature 驱动的输入租约\n // 不受影响,仍可正常回复。\n if (session.inputPolicy === 'none') {\n return {\n success: false,\n code: 'runtime_not_accepting_input',\n error: 'This runtime does not accept external user turns',\n };\n }\n const queuedInput = this.enqueueQueuedInput(session, input.text, input.images, input.source, input.sourceRef, input.capabilityActivations);\n console.log(`[Viewer Worker] 用户回合已排队: ${agentId}, source=${input.source || 'unknown'}, queueLength=${session.queuedInputs.length}`);\n return {\n success: true,\n delivery: 'queued',\n id: queuedInput.id,\n queueLength: session.queuedInputs.length,\n ...(input.source ? { source: input.source } : {}),\n ...(input.sourceRef ? { sourceRef: input.sourceRef } : {}),\n };\n }\n\n private handlePostUserTurn(req: IncomingMessage, res: ServerResponse, agentId: string): void {\n let body = '';\n req.setEncoding('utf8');\n req.on('data', (chunk) => { body += chunk; });\n req.on('end', () => {\n try {\n const result = this.submitUserTurn(agentId, JSON.parse(body) as UserTurnInput);\n const status = result.success\n ? 200\n : result.code === 'agent_not_found'\n ? 404\n : result.code === 'input_mode_conflict' || result.code === 'runtime_not_accepting_input'\n ? 409\n : 400;\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(result));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ success: false, code: 'invalid_input', error: 'Invalid request' }));\n }\n });\n }\n\n private forwardInputResponse(\n agentId: string,\n session: AgentSession,\n requestId: string,\n input: string,\n response: UserInputResponse,\n ): boolean {\n const message = JSON.stringify({\n type: 'input-response',\n agentId,\n requestId,\n input: response.text ?? input ?? '',\n response,\n }) + '\\n';\n\n const targetClientId = session.clientId;\n if (targetClientId) {\n const targetSocket = this.udsClients.get(targetClientId);\n if (targetSocket) {\n try {\n targetSocket.write(message);\n console.log(`[Viewer Worker] 输入响应已发送到 ${targetClientId}: ${requestId}`);\n return true;\n } catch (writeError) {\n console.error('[Viewer Worker] UDS 写入失败:', writeError);\n return false;\n }\n } else {\n console.warn(`[Viewer Worker] 目标客户端连接不存在: ${targetClientId}`);\n }\n return false;\n }\n\n // Runtime-scoped input must have an exact client binding. Broadcasting would\n // allow a missing/stale runtime identity to deliver input to another agent.\n console.warn(`[Viewer Worker] Agent has no clientId; refusing input delivery: ${agentId}`);\n return false;\n }\n\n private createTextInputResponse(input: UserTurnInput | QueuedInput): UserInputResponse {\n const payload: Record<string, unknown> = {};\n if (Array.isArray(input.images) && input.images.length > 0) payload.images = input.images;\n if (input.source) payload.source = input.source;\n if (input.sourceRef) payload.sourceRef = input.sourceRef;\n if (Array.isArray(input.capabilityActivations) && input.capabilityActivations.length > 0) {\n payload.capabilityActivations = input.capabilityActivations;\n }\n return {\n kind: 'text',\n text: input.text,\n ...(Object.keys(payload).length > 0 ? { payload } : {}),\n };\n }\n\n private enqueueQueuedInput(\n session: AgentSession,\n text: string,\n images?: ImageInput[],\n source?: string,\n sourceRef?: string,\n capabilityActivations?: string[],\n ): QueuedInput {\n if (!session.queuedInputs) {\n (session as any).queuedInputs = [];\n }\n const queuedInput: QueuedInput = {\n id: `q-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n text,\n timestamp: Date.now(),\n ...(Array.isArray(images) && images.length > 0 ? { images } : {}),\n ...(source ? { source } : {}),\n ...(sourceRef ? { sourceRef } : {}),\n ...(Array.isArray(capabilityActivations) && capabilityActivations.length > 0 ? { capabilityActivations } : {}),\n };\n session.queuedInputs.push(queuedInput);\n return queuedInput;\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 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 /**\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 console.warn(`[VW.handleInterrupt] no UDS socket for clientId=${targetClientId}`);\n res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n success: false,\n code: 'runtime_not_accepting_input',\n error: 'Agent runtime is not connected',\n }));\n return;\n }\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 res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n success: false,\n code: 'runtime_not_accepting_input',\n error: 'Agent runtime is not connected',\n }));\n return;\n }\n } else {\n console.warn(`[VW.handleInterrupt] session has no clientId`);\n res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({\n success: false,\n code: 'runtime_not_accepting_input',\n error: 'Agent runtime is not connected',\n }));\n return;\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 todoPlan: this.createEmptyTodoPlan(),\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, templateMounts, templateEntries, hookInspector, overview, activeInputRequest, inputPolicy } = msg;\n const session = this.getOrCreateSession(agentId, name);\n\n // 外部输入策略('none' = 拒绝排队注入,如测试沙盒)\n if (inputPolicy === 'none') {\n session.inputPolicy = 'none';\n }\n\n // 存储项目根目录(用于模板文件加载)\n if (projectRoot) {\n session.projectRoot = projectRoot;\n }\n\n // 记录所属客户端连接(用于多进程输入响应路由)\n if (clientId) {\n session.clientId = clientId;\n }\n\n // 收集模板装载载荷(mount 协议:mounts 为真实目录根数组,entries 为\n // 模板名 → {mount 下标, rel 相对路径})。协议不混版:worker 只认\n // mount 载荷,旧版字段(URL 映射)一律无视。\n if (Array.isArray(templateMounts) && templateEntries && typeof templateEntries === 'object') {\n const mounts: string[] = [];\n const entries: Record<string, { mount: number; rel: string }> = {};\n const missing: string[] = [];\n\n for (const root of templateMounts) {\n if (typeof root !== 'string' || !root) continue;\n if (!mounts.includes(root)) mounts.push(root);\n }\n for (const [templateName, entry] of Object.entries(templateEntries)) {\n const root = mounts[(entry as any)?.mount];\n const rel = (entry as any)?.rel;\n if (typeof root !== 'string' || typeof rel !== 'string') continue;\n // 注册时验证:装载条目在磁盘必须存在。缺失在这里显性化(warn),\n // 而不是留到浏览器 404 后静默降级。\n if (!existsSync(join(root, rel))) {\n missing.push(`${templateName} -> ${join(root, rel)}`);\n continue;\n }\n entries[templateName] = { mount: mounts.indexOf(root), rel };\n }\n if (missing.length > 0) {\n console.warn(`[Viewer Worker] Agent ${agentId} 注册时发现 ${missing.length} 个模板装载条目缺失: ${missing.join('; ')}`);\n }\n\n this.templatePayloads.set(agentId, { mounts, entries });\n for (const root of mounts) {\n const mountId = ViewerWorker.mountIdForRoot(root);\n let mount = this.templateMounts.get(mountId);\n if (!mount) {\n mount = { root, agents: new Set() };\n this.templateMounts.set(mountId, mount);\n } else if (mount.root !== root) {\n // 哈希碰撞(概率可忽略):拒绝注册该 root 并告警,不静默错配\n console.warn(`[Viewer Worker] mountId 碰撞: ${mountId} 已绑定 ${mount.root},拒绝 ${root}`);\n continue;\n }\n mount.agents.add(agentId);\n }\n }\n\n if (hookInspector) {\n session.hookInspector = hookInspector;\n }\n if (overview) {\n session.overview = overview;\n }\n\n // 恢复活跃的输入请求(用于重连后恢复输入框)\n if (activeInputRequest) {\n // 连接重建是状态对账而不是追加:DebugHub 给出的 lease 是这个 Agent\n // 唯一仍有本地 resolver 的输入所有者,旧连接遗留的租约必须被替换。\n session.inputLease = {\n requestId: activeInputRequest.requestId,\n prompt: activeInputRequest.prompt,\n placeholder: activeInputRequest.placeholder,\n initialValue: activeInputRequest.initialValue,\n actions: activeInputRequest.actions,\n mode: activeInputRequest.mode,\n questions: activeInputRequest.questions,\n timestamp: activeInputRequest.timestamp,\n };\n\n console.log(`[Viewer Worker] 恢复活跃输入请求: ${activeInputRequest.requestId},Agent: ${agentId}`);\n } else {\n // 注册快照是完整事实来源;没有 lease 就不能保留旧连接的输入卡。\n delete session.inputLease;\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 public handleUpdateTodoPlan(msg: { agentId: string; plan: TodoPlanSnapshot }): void {\n const { agentId, plan } = msg;\n const session = this.agentSessions.get(agentId);\n if (!session) return;\n session.todoPlan = this.normalizeTodoPlan(plan);\n this.updateSessionActivity(agentId);\n }\n\n /**\n * 清空 Feature 模板装载(当 Agent 断开连接时调用):\n * 移除该 agent 的载荷,并对 mount 注册表做引用计数回收。\n */\n private clearFeatureTemplates(agentId: string): void {\n this.templatePayloads.delete(agentId);\n for (const [mountId, mount] of this.templateMounts) {\n mount.agents.delete(agentId);\n if (mount.agents.size === 0) {\n this.templateMounts.delete(mountId);\n }\n }\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 createEmptyTodoPlan(): TodoPlanSnapshot {\n return {\n feature: 'todo',\n updatedAt: 0,\n counter: 0,\n tasks: [],\n summary: {\n total: 0,\n pending: 0,\n inProgress: 0,\n completed: 0,\n cancelled: 0,\n },\n interruptTargetId: null,\n forceContinue: null,\n };\n }\n\n private normalizeTodoPlan(plan?: TodoPlanSnapshot | null): TodoPlanSnapshot {\n const fallback = this.createEmptyTodoPlan();\n if (!plan || typeof plan !== 'object') return fallback;\n const normalizeStatus = (status: unknown): TodoTaskSnapshot['status'] => {\n return status === 'in_progress' || status === 'completed' || status === 'deleted'\n ? status\n : 'pending';\n };\n const tasks = Array.isArray(plan.tasks) ? plan.tasks.map(task => ({\n id: String(task.id ?? ''),\n subject: String(task.subject ?? ''),\n description: String(task.description ?? ''),\n status: normalizeStatus(task.status),\n metadata: task.metadata && typeof task.metadata === 'object' ? task.metadata : undefined,\n createdAt: typeof task.createdAt === 'number' ? task.createdAt : 0,\n updatedAt: typeof task.updatedAt === 'number' ? task.updatedAt : 0,\n })).filter(task => task.id) : [];\n const summary = plan.summary || fallback.summary;\n return {\n feature: 'todo',\n updatedAt: typeof plan.updatedAt === 'number' ? plan.updatedAt : Date.now(),\n counter: typeof plan.counter === 'number' ? plan.counter : tasks.length,\n tasks,\n summary: {\n total: typeof summary.total === 'number' ? summary.total : tasks.length,\n pending: typeof summary.pending === 'number' ? summary.pending : tasks.filter(t => t.status === 'pending').length,\n inProgress: typeof summary.inProgress === 'number' ? summary.inProgress : tasks.filter(t => t.status === 'in_progress').length,\n completed: typeof summary.completed === 'number' ? summary.completed : tasks.filter(t => t.status === 'completed').length,\n cancelled: typeof summary.cancelled === 'number' ? summary.cancelled : tasks.filter(t => t.status === 'deleted').length,\n },\n interruptTargetId: typeof plan.interruptTargetId === 'string' ? plan.interruptTargetId : null,\n forceContinue: plan.forceContinue && typeof plan.forceContinue === 'object' ? {\n enabled: plan.forceContinue.enabled === true,\n consecutive: typeof plan.forceContinue.consecutive === 'number' ? plan.forceContinue.consecutive : 0,\n max: typeof plan.forceContinue.max === 'number' ? plan.forceContinue.max : 3,\n } : null,\n };\n }\n\n private createEmptyRuntimeState(): AgentRuntimeStateSnapshot {\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 lastOutcome: null,\n };\n }\n\n private cloneRuntimeState(snapshot?: AgentRuntimeStateSnapshot | null): AgentRuntimeStateSnapshot {\n const source = snapshot || this.createEmptyRuntimeState();\n return {\n ...source,\n activeToolNames: Array.isArray(source.activeToolNames) ? source.activeToolNames.slice() : [],\n streamToolNames: Array.isArray(source.streamToolNames) ? source.streamToolNames.slice() : undefined,\n };\n }\n\n private getRuntimeStageFromLLMPhase(phase: string): AgentRuntimeStateSnapshot['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: AgentRuntimeStateSnapshot,\n nextStage: AgentRuntimeStateSnapshot['stage'],\n timestamp: number,\n ): AgentRuntimeStateSnapshot {\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): AgentRuntimeStateSnapshot {\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\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\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 streamToolNames: undefined,\n };\n } else if (notification.type === 'call.finish') {\n // 结构化终态(CallOutcome):status/reason/error 由框架产出,\n // 不再从文本推断;lastError* 与 lastOutcome 一并进入 runtime snapshot。\n const outcome = (notification.data && typeof notification.data === 'object')\n ? notification.data as {\n status?: string;\n reason?: string;\n error?: { category?: string; message?: string } | null;\n }\n : {};\n const status = typeof outcome.status === 'string' ? outcome.status : '';\n const completed = status === 'completed';\n const cancelled = status === 'cancelled' || status === 'continued';\n const nextStage = completed\n ? 'completed'\n : cancelled\n ? 'cancelled'\n : 'failed';\n session.callActive = false;\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, nextStage, notification.timestamp || Date.now()),\n callActive: false,\n activeToolNames: [],\n activeToolCount: 0,\n streamToolNames: undefined,\n retryAttempt: undefined,\n maxRetries: undefined,\n nextRetryDelayMs: undefined,\n lastErrorType: outcome.error?.category ?? null,\n lastErrorMessage: outcome.error?.message ?? null,\n lastOutcome: notification.data as AgentRuntimeStateSnapshot['lastOutcome'],\n };\n } else if (notification.type === 'llm.retry') {\n // 适配器内部重试观测:waiting → retry_waiting(含退避参数),\n // requesting → retry_requesting。字段与 retryAttempt/maxRetries/\n // nextRetryDelayMs 快照字段一一对应。\n const data = (notification.data && typeof notification.data === 'object')\n ? notification.data as Record<string, unknown>\n : {};\n const phase = data.phase === 'requesting' ? 'retry_requesting' : 'retry_waiting';\n const attempt = typeof data.attempt === 'number' ? data.attempt : undefined;\n const maxRetries = typeof data.maxRetries === 'number' ? data.maxRetries : undefined;\n const delayMs = typeof data.delayMs === 'number' ? data.delayMs : undefined;\n session.runtimeState = {\n ...this.updateRuntimeStage(runtimeState, phase, notification.timestamp || Date.now()),\n callActive: session.callActive === true,\n ...(attempt !== undefined ? { retryAttempt: attempt } : {}),\n ...(maxRetries !== undefined ? { maxRetries } : {}),\n ...(delayMs !== undefined ? { nextRetryDelayMs: delayMs } : {}),\n ...(typeof data.errorType === 'string'\n ? { lastErrorType: data.errorType, lastErrorMessage: null }\n : {}),\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 streamToolNames = Array.isArray(data.streamToolNames)\n ? data.streamToolNames.map((n) => String(n || '')).filter(Boolean)\n : undefined;\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 ...(streamToolNames ? { streamToolNames } : { streamToolNames: undefined }),\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 streamToolNames: undefined,\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 || null;\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 session = selectedAgentId ? this.agentSessions.get(selectedAgentId) : 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 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 // 活跃 call 最后一次 drain 与 call.finish 之间仍可能有新输入入队。\n // 下一次兼容的文本请求出现时在框架层接力,封闭这个生命周期竞态。\n if (!session.inputLease && msg.mode !== 'choices' && session.queuedInputs.length > 0) {\n const queuedInput = session.queuedInputs[0];\n const response = this.createTextInputResponse(queuedInput);\n if (this.forwardInputResponse(agentId, session, requestId, queuedInput.text, response)) {\n session.queuedInputs.shift();\n console.log(`[Viewer Worker] 排队用户回合已转交给新输入请求: ${requestId}, remaining=${session.queuedInputs.length}`);\n return;\n }\n }\n\n // 输入请求是覆盖式的唯一 lease;同一 Agent 永远不会在 Worker 中累积\n // 多张可提交卡片。DebugHub 端也持有同样的不变量以防并发来源。\n const lease: InputLease = {\n 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 session.inputLease = lease;\n\n console.log(`[Viewer Worker] Input lease 已存储: ${requestId}`);\n }\n\n /**\n * 处理输入请求取消(运行时中断/销毁时由 DebugHub 发出)\n * 只有 requestId 匹配当前租约时才清除,避免误删紧随其后重开的新租约。\n */\n public handleInputRequestCancelled(msg: InputRequestCancelledMsg): void {\n const session = this.agentSessions.get(msg.agentId);\n if (!session?.inputLease || session.inputLease.requestId !== msg.requestId) {\n return;\n }\n delete session.inputLease;\n console.log(`[Viewer Worker] 输入租约已取消: agentId=${msg.agentId}, requestId=${msg.requestId}`);\n }\n\n /**\n * mountId:装载根目录的稳定哈希(sha1 前 12 hex)。与 agent 无关,\n * 同一 root 跨 agent / 跨注册复用同一 ID,浏览器模块缓存天然共享。\n */\n private static mountIdForRoot(root: string): string {\n return createHash('sha1').update(root.split('\\\\').join('/').toLowerCase()).digest('hex').slice(0, 12);\n }\n\n /**\n * /tpl/{mountId}/{rel...} — 模板装载资产服务。\n * mount root 下的字节级镜像:模板文件、tsup 共享 chunk、sourcemap 同构命中。\n * 唯一安全边界:resolve 后必须仍在 mount root 内(路径穿越防护)。\n */\n public handleTplAsset(req: IncomingMessage, res: ServerResponse, url: string): void {\n try {\n const match = url.match(/^\\/tpl\\/([0-9a-f]{12})\\/(.+)$/);\n if (!match) {\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Invalid template mount path');\n return;\n }\n const [, mountId, rel] = match;\n\n const mount = this.templateMounts.get(mountId);\n if (!mount) {\n console.warn(`[Viewer Worker] /tpl/ 未注册 mountId: ${mountId} (url=${url})`);\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end(`Unknown template mount: ${mountId}`);\n return;\n }\n\n // 路径穿越防护:解析后的绝对路径不得逃出 mount root\n // (win32 盘符大小写不敏感,前缀比较统一小写)\n const rootAbs = resolve(mount.root);\n const target = resolve(rootAbs, rel);\n const targetLower = target.toLowerCase();\n const rootLower = rootAbs.toLowerCase();\n if (targetLower !== rootLower && !targetLower.startsWith(rootLower + sep)) {\n res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Forbidden path');\n return;\n }\n\n readFile(target, (err: any, data: Buffer) => {\n if (err) {\n console.warn(`[Viewer Worker] /tpl/ 文件缺失: ${target} (url=${url})`);\n res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Template asset not found');\n return;\n }\n const ext = extname(target).toLowerCase();\n const contentType = ext === '.js' || ext === '.mjs'\n ? 'application/javascript; charset=utf-8'\n : ext === '.json' || ext === '.map'\n ? 'application/json; charset=utf-8'\n : ext === '.css'\n ? 'text/css; charset=utf-8'\n : 'application/octet-stream';\n res.writeHead(200, {\n 'Content-Type': contentType,\n // 开发态(junction 指向框架仓库)dist 会在运行中重建,chunk 名随内容\n // 变化;协商缓存保证重建后浏览器拿到新文件,同时保留 304 能力。\n 'Cache-Control': 'no-cache',\n });\n res.end(data);\n });\n } catch (err: any) {\n console.error('[Viewer Worker] /tpl/ 处理错误:', 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 '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 'input-request-cancelled':\n worker.handleInputRequestCancelled(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"],"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;;;ACAnB,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACApB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAxB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAvB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAxB,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACA5B,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAvB,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAtB,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAjC,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAzB,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACA1B,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACA5B,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAzB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAvB,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAhC,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACA/B,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["QUERY_LOGS_DEFAULT_UNBOUNDED_LIMIT","resolve"]}
|