ai-helper-agent 2.0.0 → 2.2.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.
Files changed (38) hide show
  1. package/README.md +28 -5
  2. package/README.zh-CN.md +28 -5
  3. package/bin/agent.js +150 -0
  4. package/dist/assets/{agent-store-BFaBECYz.js → agent-store-DOyMdB_7.js} +2 -2
  5. package/dist/assets/{agent-store-BFaBECYz.js.map → agent-store-DOyMdB_7.js.map} +1 -1
  6. package/dist/assets/background.js +86 -87
  7. package/dist/assets/background.js.map +1 -1
  8. package/dist/assets/content.js +84 -80
  9. package/dist/assets/{i18n-CohqPkkN.js → i18n-MmYITlVj.js} +9 -7
  10. package/dist/assets/i18n-MmYITlVj.js.map +1 -0
  11. package/dist/assets/model-profiles-CV_sUuPf.js +25 -0
  12. package/dist/assets/model-profiles-CV_sUuPf.js.map +1 -0
  13. package/dist/assets/options-Df_1Dtn8.css +1 -0
  14. package/dist/assets/options.js +78 -63
  15. package/dist/assets/options.js.map +1 -1
  16. package/dist/assets/page-snapshot-renderer-CE53ibi-.js +4 -0
  17. package/dist/assets/page-snapshot-renderer-CE53ibi-.js.map +1 -0
  18. package/dist/assets/session-store-B0ZPGm7d.js +77 -0
  19. package/dist/assets/session-store-B0ZPGm7d.js.map +1 -0
  20. package/dist/assets/side_panel.css +1 -1
  21. package/dist/assets/side_panel.js +478 -439
  22. package/dist/assets/side_panel.js.map +1 -1
  23. package/dist/icons/badges/version.svg +4 -4
  24. package/dist/manifest.json +6 -3
  25. package/dist/offscreen/offscreen.js +10 -8
  26. package/dist/options.html +3 -12
  27. package/dist/side_panel.html +120 -32
  28. package/package.json +1 -1
  29. package/src/install-mode.js +128 -0
  30. package/src/locales/cli.json +24 -0
  31. package/src/rag/install.js +4 -0
  32. package/src/server.js +3 -1
  33. package/dist/assets/i18n-CohqPkkN.js.map +0 -1
  34. package/dist/assets/model-profiles-BS2LIjxf.js +0 -2
  35. package/dist/assets/model-profiles-BS2LIjxf.js.map +0 -1
  36. package/dist/assets/options-CE9-fjJV.css +0 -1
  37. package/dist/assets/session-store-DwXNnVF5.js +0 -100
  38. package/dist/assets/session-store-DwXNnVF5.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"mappings":"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","names":["idb.deleteReactCheckpoint","idb.ensureMigration","idb.getAllSessions","idb.getActiveSessionId","state","idb.getSession","idb.putSession","idb.setActiveSessionId","idb.deleteSession","idb.getAllBookmarks","idb.deleteBookmark","idb.getAllArchivedSessions","idb.replaceArchivedSessions"],"ignoreList":[],"sources":["../../src/shared/token-counter.js","../../src/storage/db.js","../../src/storage/token-store.js","../../src/shared/agent-defaults.js","../../src/side_panel/state.js","../../src/storage/session-store.js"],"sourcesContent":["import logger from './logger.js';\nimport { t, registerTranslations } from './i18n.js';\n\n// shared/token-counter.js - Token 估算工具\n// 使用字符数估算 token 数,无需引入 tiktoken 等重量级依赖\n\nregisterTranslations('zh', {\n tokenCounter: {\n truncatedMiddle: '\\n\\n... [中间 {tokens} tokens 已截断] ...\\n\\n',\n omittedTokens: '\\n\\n... [省略 {tokens} tokens] ...\\n\\n',\n omittedMiddleHtml: '\\n<!-- 省略中间内容 -->\\n',\n truncated: '[截断]',\n omittedKey: '...[省略]',\n moreFields: '还有 {count} 个字段',\n unknownTool: '未知工具',\n userQuestion: '- 用户问题:{question}',\n usedTools: '- 使用的工具:{tools}',\n historySummary: '[历史摘要]',\n },\n});\n\nregisterTranslations('en', {\n tokenCounter: {\n truncatedMiddle: '\\n\\n... [middle {tokens} tokens truncated] ...\\n\\n',\n omittedTokens: '\\n\\n... [{tokens} tokens omitted] ...\\n\\n',\n omittedMiddleHtml: '\\n<!-- middle content omitted -->\\n',\n truncated: '[truncated]',\n omittedKey: '...[omitted]',\n moreFields: '{count} more fields',\n unknownTool: 'Unknown tool',\n userQuestion: '- User question: {question}',\n usedTools: '- Tools used: {tools}',\n historySummary: '[History Summary]',\n },\n});\n\n// 估算常量:中文约 1.5 字符/token,英文约 4 字符/token\n// 参考 DeepSeek tokenizer 的行为特征\nconst CHINESE_CHAR_REGEX = /[\\u4e00-\\u9fff\\u3400-\\u4dbf\\uf900-\\ufaff]/g;\nconst CHARS_PER_TOKEN_CN = 1.5;\nconst CHARS_PER_TOKEN_EN = 4;\n\n// 每条消息的 role + 结构开销(约 4 tokens)\nconst MESSAGE_OVERHEAD = 4;\n\n// ============================================================\n// 图片 Token 估算(参考 OpenAI Vision 高分辨率模式)\n// 多数多模态模型(OpenAI/Claude/DeepSeek VL 等)的图片 token 与分辨率相关,\n// 而非 Base64 字符串长度。按 Base64 字符串估算会严重高估(约 20 倍)。\n// 公式:tiles = ceil(w/512) * ceil(h/512),tokens = 85 + 170 * tiles\n// ============================================================\nconst IMAGE_BASE_TOKENS = 85;\nconst IMAGE_TOKENS_PER_TILE = 170;\nconst IMAGE_TILE_SIZE = 512;\n\n/**\n * 估算单张图片的 token 数量\n * 优先用压缩后尺寸(width/height)按 tile 公式计算;\n * 缺失尺寸时按 Base64 字节大小兜底估算(经验值:JPEG 约 65 bytes/token)\n * @param {{ url?: string, width?: number, height?: number }} imageUrlObj\n * @returns {number}\n */\nfunction estimateImageTokens(imageUrlObj) {\n if (!imageUrlObj) return IMAGE_BASE_TOKENS;\n\n const w = Number(imageUrlObj.width);\n const h = Number(imageUrlObj.height);\n if (w > 0 && h > 0) {\n const tiles = Math.ceil(w / IMAGE_TILE_SIZE) * Math.ceil(h / IMAGE_TILE_SIZE);\n return IMAGE_BASE_TOKENS + IMAGE_TOKENS_PER_TILE * tiles;\n }\n\n // 兜底:按 Base64 字节估算(无尺寸信息,如旧数据)\n // 50KB JPEG ≈ 765 tokens(OpenAI),故 1 token ≈ 65 bytes\n const url = imageUrlObj.url || '';\n const base64Data = url.split(',')[1] || url;\n const bytes = (base64Data.length * 3) / 4; // Base64 每 4 字符 ≈ 3 字节\n return Math.max(IMAGE_BASE_TOKENS, Math.ceil(bytes / 65));\n}\n\n// ============================================================\n// 实时校准:基于 API 实际返回的 prompt_tokens 修正估算偏差\n// 使用指数加权移动平均(EWMA)平滑,避免单次异常波动\n// ============================================================\nlet calibrationFactor = 1.0;\nlet calibrationSamples = 0;\nconst CALIBRATION_MIN_SAMPLES = 3; // 至少 3 次采样后才启用校准\nconst CALIBRATION_RATIO_MIN = 0.3; // 异常比例下限\nconst CALIBRATION_RATIO_MAX = 3.0; // 异常比例上限\n\n/**\n * 更新 token 估算校准因子\n * 在每次 API 调用完成后,用 API 返回的实际 prompt_tokens 与我们的估算值对比\n * @param {number} estimated - 我们的估算值(不含输出预留)\n * @param {number} actual - API 返回的 prompt_tokens\n */\nexport function updateCalibration(estimated, actual) {\n if (!actual || actual <= 0 || !estimated || estimated <= 0) return;\n\n const ratio = actual / estimated;\n // 过滤异常比例(如估算或实际值明显错误)\n if (ratio < CALIBRATION_RATIO_MIN || ratio > CALIBRATION_RATIO_MAX) {\n logger.warn(`[TokenCounter] calibration ratioexception,ignore: estimated=${estimated}, actual=${actual}, ratio=${ratio.toFixed(3)}`);\n return;\n }\n\n calibrationSamples++;\n // EWMA:样本少时权重高(快速收敛),样本多时权重低(稳定)\n const alpha = Math.min(0.3, 1 / calibrationSamples);\n const oldFactor = calibrationFactor;\n calibrationFactor = calibrationFactor * (1 - alpha) + ratio * alpha;\n\n logger.debug(`[TokenCounter] Tokencalibration update: ratio=${ratio.toFixed(3)}, alpha=${alpha.toFixed(3)}, old=${oldFactor.toFixed(3)} → new=${calibrationFactor.toFixed(3)}, samples=${calibrationSamples}`);\n}\n\n/**\n * 获取校准后的 token 估算\n * 样本不足时不校准(返回原始估算值)\n * @param {number} rawEstimate - 原始估算值\n * @returns {number}\n */\nexport function getCalibratedTokens(rawEstimate) {\n if (calibrationSamples < CALIBRATION_MIN_SAMPLES || calibrationFactor <= 0) {\n return rawEstimate;\n }\n return Math.ceil(rawEstimate * calibrationFactor);\n}\n\n/**\n * 获取当前校准状态信息(用于诊断/日志)\n * @returns {{ factor: number, samples: number, active: boolean }}\n */\nexport function getCalibrationInfo() {\n return {\n factor: calibrationFactor,\n samples: calibrationSamples,\n active: calibrationSamples >= CALIBRATION_MIN_SAMPLES\n };\n}\n\n/**\n * 估算单段文本的 token 数量\n * @param {string} text\n * @returns {number}\n */\nexport function estimateTokens(text) {\n if (!text || typeof text !== 'string') return 0;\n const chineseMatches = text.match(CHINESE_CHAR_REGEX);\n const chineseChars = chineseMatches ? chineseMatches.length : 0;\n const otherChars = text.length - chineseChars;\n return Math.ceil(chineseChars / CHARS_PER_TOKEN_CN + otherChars / CHARS_PER_TOKEN_EN);\n}\n\n/**\n * 估算消息数组的总 token 数\n * 支持 multipart content(文本 + 图片):图片按分辨率估算,避免 Base64 字符串高估\n * @param {Array<{role: string, content: string|Array, tool_calls?: Array, tool_call_id?: string, reasoning_content?: string}>} messages\n * @returns {number}\n */\nexport function estimateMessagesTokens(messages) {\n if (!messages || messages.length === 0) return 0;\n return messages.reduce((sum, m) => {\n let tokens = 0;\n if (typeof m.content === 'string') {\n tokens = estimateTokens(m.content);\n } else if (Array.isArray(m.content)) {\n // 多模态 content:分别按 part 类型估算\n for (const part of m.content) {\n if (!part) continue;\n if (part.type === 'text') {\n tokens += estimateTokens(part.text || '');\n } else if (part.type === 'image_url' && part.image_url) {\n // 图片按分辨率估算,不把 Base64 字符串计入\n tokens += estimateImageTokens(part.image_url);\n } else {\n tokens += estimateTokens(JSON.stringify(part));\n }\n }\n } else if (m.content) {\n tokens = estimateTokens(JSON.stringify(m.content));\n }\n\n // 附加字段 token\n let extra = '';\n if (m.tool_calls) extra += JSON.stringify(m.tool_calls);\n if (m.tool_call_id) extra += m.tool_call_id;\n if (m.reasoning_content) extra += m.reasoning_content;\n if (extra) tokens += estimateTokens(extra);\n\n return sum + tokens + MESSAGE_OVERHEAD;\n }, 0);\n}\n\n/**\n * 按 token 上限截断文本,保留头部和尾部\n * @param {string} content - 原始文本\n * @param {number} maxTokens - 最大 token 数\n * @returns {string}\n */\nexport function truncateByTokens(content, maxTokens) {\n if (!content || typeof content !== 'string') return content;\n const currentTokens = estimateTokens(content);\n if (currentTokens <= maxTokens) return content;\n\n // 保留头部 70%,尾部 30%\n const headTokens = Math.floor(maxTokens * 0.7);\n const tailTokens = maxTokens - headTokens;\n\n const headChars = Math.floor(headTokens * CHARS_PER_TOKEN_EN);\n const tailChars = Math.floor(tailTokens * CHARS_PER_TOKEN_EN);\n\n const head = content.slice(0, headChars);\n const tail = content.slice(-tailChars);\n\n const truncatedTokens = currentTokens - maxTokens;\n return head + t('tokenCounter.truncatedMiddle', { tokens: truncatedTokens }) + tail;\n}\n\n/**\n * 智能工具结果截断:根据内容类型采用不同的保留策略\n * - HTML: 优先保留 <body> 内容\n * - JSON: 保留顶层 key 结构,值摘要\n * - 代码: 保留头部 + 关键结构 + 尾部\n * - 纯文本: 固定 60% / 20% 头尾截断\n * @param {string} content - 原始内容\n * @param {number} maxTokens - 最大 token 数\n * @param {string} [contentType] - 内容类型提示: 'html' | 'json' | 'code' | 'text'\n * @returns {string}\n */\nexport function truncateContentSmart(content, maxTokens, contentType) {\n if (!content || typeof content !== 'string') return content;\n if (estimateTokens(content) <= maxTokens) return content;\n\n // 自动检测类型\n if (!contentType) {\n const trimmed = content.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n contentType = 'json';\n } else if (/<(!DOCTYPE|html|head|body)[\\s>]/i.test(trimmed.substring(0, 200))) {\n contentType = 'html';\n } else {\n contentType = 'text';\n }\n }\n\n if (contentType === 'html') {\n return truncateHtmlSmart(content, maxTokens);\n } else if (contentType === 'json') {\n return truncateJsonSmart(content, maxTokens);\n }\n // text/code: 保留前 60% 字符 + 后 20% 字符\n const headChars = Math.floor(maxTokens * 0.6 * CHARS_PER_TOKEN_EN);\n const tailChars = Math.floor(maxTokens * 0.2 * CHARS_PER_TOKEN_EN);\n const head = content.slice(0, headChars);\n const tail = content.slice(-tailChars);\n return head + t('tokenCounter.omittedTokens', { tokens: estimateTokens(content) - maxTokens }) + tail;\n}\n\n/**\n * HTML 智能截断:优先保留 <body> 内容\n */\nfunction truncateHtmlSmart(content, maxTokens) {\n const bodyMatch = content.match(/<body[\\s>][\\s\\S]*<\\/body>/i);\n if (bodyMatch) {\n const bodyContent = bodyMatch[0];\n const headContent = content.substring(0, content.indexOf(bodyContent));\n // body 内容占 80%,头部占 20%\n const bodyBudget = Math.floor(maxTokens * 0.8);\n const headBudget = maxTokens - bodyBudget;\n const headChars = Math.floor(headBudget * CHARS_PER_TOKEN_EN);\n const bodyChars = Math.floor(bodyBudget * CHARS_PER_TOKEN_EN);\n return content.slice(0, headChars) +\n bodyContent.slice(0, bodyChars) +\n t('tokenCounter.omittedMiddleHtml') +\n bodyContent.slice(-Math.floor(bodyChars * 0.3)) +\n content.slice(-Math.floor(headChars * 0.3));\n }\n // 无 body 标签,fallback 到通用截断\n return truncateByTokens(content, maxTokens);\n}\n\n/**\n * JSON 智能截断:保留顶层 key 结构,值摘要\n */\nfunction truncateJsonSmart(content, maxTokens) {\n try {\n const obj = JSON.parse(content);\n if (typeof obj !== 'object' || obj === null) {\n return truncateByTokens(content, maxTokens);\n }\n\n const summarized = {};\n const keys = Object.keys(obj);\n const maxKeys = Math.min(keys.length, 30);\n\n for (let i = 0; i < maxKeys; i++) {\n const key = keys[i];\n const val = obj[key];\n if (typeof val === 'string' && val.length > 200) {\n summarized[key] = val.substring(0, 200) + '...' + t('tokenCounter.truncated');\n } else if (typeof val === 'object' && val !== null) {\n summarized[key] = `[${Array.isArray(val) ? `Array(${val.length})` : `Object(${Object.keys(val).length} keys)`}]`;\n } else {\n summarized[key] = val;\n }\n }\n\n if (keys.length > maxKeys) {\n summarized['...[省略]'] = `还有 ${keys.length - maxKeys} 个字段`;\n }\n\n let result = JSON.stringify(summarized, null, 2);\n // 如果摘要后仍超预算,做最终字符截断\n if (estimateTokens(result) > maxTokens) {\n result = result.substring(0, Math.floor(maxTokens * CHARS_PER_TOKEN_EN));\n }\n return result;\n } catch {\n return truncateByTokens(content, maxTokens);\n }\n}\n\n/**\n * 模型上下文窗口配置(单位:tokens)\n * 作为内置默认值,可通过添加自定义模型时设置上下文窗口来覆盖\n */\nexport const MODEL_CONTEXT_WINDOWS = {\n 'deepseek-v4-pro': 128000,\n 'deepseek-v3': 128000,\n 'deepseek-chat': 64000,\n 'deepseek-reasoner': 64000,\n 'gpt-4o': 128000,\n 'gpt-4o-mini': 128000,\n 'gpt-4-turbo': 128000,\n 'gpt-4': 8192,\n 'gpt-3.5-turbo': 16385,\n 'claude-3.5-sonnet': 200000,\n 'claude-3-opus': 200000,\n 'claude-3-haiku': 200000,\n 'claude-3-sonnet': 200000,\n default: 256000\n};\n\n/**\n * 标准化自定义模型配置数组,向前兼容旧格式(string[] → object[])\n * @param {Array<string|{name: string, contextWindow?: number}>} customModels\n * @returns {Map<string, number>} modelName → contextWindow 的映射\n */\nexport function normalizeCustomModels(customModels) {\n const map = new Map();\n if (!customModels || !Array.isArray(customModels)) return map;\n for (const item of customModels) {\n if (typeof item === 'string') {\n // 旧格式:仅模型名,上下文窗口未知,不加入 map(回退到内置映射)\n continue;\n }\n if (item && typeof item === 'object' && item.name) {\n map.set(item.name, item.contextWindow || 0);\n }\n }\n return map;\n}\n\n/**\n * 获取模型上下文窗口大小\n * 优先级:自定义模型配置 > 用户全局配置 > 内置映射 > default\n * @param {string} modelName\n * @param {number} [userConfiguredWindow] - 用户手动配置的上下文窗口(全局覆盖)\n * @param {Map<string, number>} [customModelMap] - 自定义模型的上下文窗口映射\n * @returns {number}\n */\nexport function getContextWindow(modelName, userConfiguredWindow, customModelMap) {\n // 1. 优先使用自定义模型中该模型单独配置的上下文窗口\n if (customModelMap && customModelMap.has(modelName)) {\n const window = customModelMap.get(modelName);\n if (window && window > 0) return window;\n }\n // 2. 全局上下文窗口覆盖\n if (userConfiguredWindow && userConfiguredWindow > 0) return userConfiguredWindow;\n // 3. 内置模型映射\n return MODEL_CONTEXT_WINDOWS[modelName] || MODEL_CONTEXT_WINDOWS.default;\n}\n\n// ============================================================\n// 上下文预算分配\n// ============================================================\n\n// 系统提示词预留 token 数\nexport const SYSTEM_PROMPT_BUDGET = 2000;\n\n// 工具定义预留 token 数(工具数量 * 平均每个工具定义 ~200 tokens)\nexport function estimateToolsTokens(toolCount) {\n return toolCount * 200;\n}\n\n// 输出预留 token 数(给模型生成回答的空间)\nexport const OUTPUT_BUDGET = 4096;\n\n/**\n * 计算可用于消息历史的 token 预算\n * @param {string} modelName\n * @param {number} toolCount\n * @param {number} [userConfiguredWindow]\n * @param {Map<string, number>} [customModelMap] - 自定义模型的上下文窗口映射\n * @returns {number}\n */\nexport function getMessageBudget(modelName, toolCount = 0, userConfiguredWindow, customModelMap) {\n const contextWindow = getContextWindow(modelName, userConfiguredWindow, customModelMap);\n return contextWindow - SYSTEM_PROMPT_BUDGET - estimateToolsTokens(toolCount) - OUTPUT_BUDGET;\n}\n\n/**\n * 评估上下文压力等级\n * @param {number} usedTokens\n * @param {number} budget\n * @returns {{ level: 'safe'|'warning'|'critical', ratio: number }}\n */\nexport function assessContextPressure(usedTokens, budget) {\n const ratio = usedTokens / budget;\n if (ratio < 0.7) return { level: 'safe', ratio };\n if (ratio < 0.9) return { level: 'warning', ratio };\n return { level: 'critical', ratio };\n}\n\n/**\n * 基于 token 预算裁剪历史消息(从旧到新移除,确保 tool_calls/tool 配对)\n * 返回裁剪后的消息数组 + 被裁剪掉的消息摘要信息\n * @param {Array} messages - 包含 system message 的完整消息数组\n * @param {number} budget - token 预算上限\n * @param {Object} [options]\n * @param {boolean} [options.preserveSystem=true] - 是否保留 system message\n * @param {boolean} [options.generateSummary=true] - 是否生成被裁剪消息的摘要\n * @returns {{ messages: Array, trimmedCount: number, summary: string|null }}\n */\nexport function trimMessagesByBudget(messages, budget, options = {}) {\n const { preserveSystem = true, generateSummary = true } = options;\n const originalLen = messages.length;\n\n if (estimateMessagesTokens(messages) <= budget) {\n return { messages: [...messages], trimmedCount: 0, summary: null };\n }\n\n const systemMsg = preserveSystem && messages[0]?.role === 'system' ? [messages[0]] : [];\n const rest = systemMsg.length ? [...messages.slice(1)] : [...messages];\n\n const trimmedMessages = [];\n\n while (rest.length > 0) {\n const currentTokens = estimateMessagesTokens([...systemMsg, ...rest]);\n if (currentTokens <= budget) break;\n\n const removed = rest.shift();\n trimmedMessages.push(removed);\n\n // 如果移除的是 assistant(tool_calls),则后续的 tool 消息也要一并移除\n if (removed?.role === 'assistant' && removed.tool_calls) {\n while (rest.length > 0 && rest[0]?.role === 'tool') {\n trimmedMessages.push(rest.shift());\n }\n }\n }\n\n const result = [...systemMsg, ...rest];\n const summary = generateSummary ? generateMessagesSummary(trimmedMessages) : null;\n\n return {\n messages: result,\n trimmedCount: originalLen - result.length,\n summary\n };\n}\n\n/**\n * 从被裁剪的消息中生成规则式结构化摘要\n * @param {Array} trimmedMessages - 被裁剪的消息\n * @returns {string|null}\n */\nexport function generateMessagesSummary(trimmedMessages) {\n if (!trimmedMessages || trimmedMessages.length === 0) return null;\n\n const summaryParts = [];\n const userQuestions = [];\n const toolCalls = [];\n\n for (const msg of trimmedMessages) {\n if (msg.role === 'user') {\n const text = typeof msg.content === 'string'\n ? msg.content\n : (Array.isArray(msg.content)\n ? msg.content.filter(c => c.type === 'text').map(c => c.text).join('')\n : '');\n if (text && text.trim()) {\n const question = text.replace(/\\[选中内容\\]\\u000a[\\s\\S]*?\\u000a\\u000a\\[用户问题\\]\\u000a/, '')\n .replace(/\\[引用内容\\]\\u000a[\\s\\S]*?\\u000a\\u000a\\[用户问题\\]\\u000a/, '')\n .replace(/\\[选中内容摘要\\]\\u000a[\\s\\S]*?\\u000a\\u000a\\[用户问题\\]\\u000a/, '')\n .replace(/\\[引用内容摘要\\]\\u000a[\\s\\S]*?\\u000a\\u000a\\[用户问题\\]\\u000a/, '')\n .trim()\n .substring(0, 80);\n if (question) userQuestions.push(question);\n }\n } else if (msg.role === 'assistant' && msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n const name = tc.function?.name || tc.name || t('tokenCounter.unknownTool');\n toolCalls.push(name);\n }\n }\n }\n\n if (userQuestions.length > 0) {\n summaryParts.push(t('tokenCounter.userQuestion', { question: userQuestions[userQuestions.length - 1] }));\n }\n if (toolCalls.length > 0) {\n // 去重并限制数量\n const uniqueTools = [...new Set(toolCalls)].slice(0, 10);\n summaryParts.push(t('tokenCounter.usedTools', { tools: uniqueTools.join(', ') }));\n }\n\n if (summaryParts.length === 0) return null;\n return t('tokenCounter.historySummary') + '\\n' + summaryParts.join('\\n');\n}\n\n/**\n * 清洗消息 content 字符串中的非法 Unicode 字符\n * - 替换孤立代理对字符(lone surrogates,由 substring 截断 emoji 等导致)为 U+FFFD\n * - 某些 API 服务端 JSON 解析器(如支持 \\x 扩展的解析器)会因非法 Unicode 而解析失败\n * @param {string} str - 原始字符串\n * @returns {string} 清洗后的字符串\n */\nfunction sanitizeContentForApi(str) {\n if (typeof str !== 'string') return str;\n // 匹配:\n // 1. 高位代理 U+D800..U+DBFF 后面没有紧跟低位代理\n // 2. 低位代理 U+DC00..U+DFFF 前面没有高位代理\n // 使用 Unicode property escapes 可读性更好,但兼容性考虑用显式范围\n return str.replace(/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g, '\\uFFFD');\n}\n\nconst API_ALLOWED_FIELDS = new Set(['role', 'content', 'tool_calls', 'tool_call_id', 'name', 'reasoning_content', 'prefix']);\n\n/**\n * 过滤消息中的内部字段,确保消息格式符合 API 要求\n * 同时扫描并修复 assistant(tool_calls) 与 tool 消息的配对关系\n * @param {Array} messages - 原始消息数组\n * @returns {Array} 过滤后的消息数组\n */\nexport function filterApiMessages(messages) {\n const filtered = messages.map((msg, index) => {\n const result = {};\n for (const key of API_ALLOWED_FIELDS) {\n if (key in msg) {\n result[key] = msg[key];\n }\n }\n\n // 清理 multipart content 中 image_url 的内部字段\n // filterApiMessages 负责剥离 original_url(原图 Base64),保留 width/height 供 token 估算\n // 此函数在 token 估算之前调用,width/height 用于 estimateImageTokens 按分辨率精准估算(避免按 Base64 字节高估约 20 倍)\n if (Array.isArray(result.content)) {\n result.content = result.content.map(part => {\n if (part && part.type === 'image_url' && part.image_url) {\n const clean = { url: part.image_url.url };\n if (part.image_url.width != null) clean.width = part.image_url.width;\n if (part.image_url.height != null) clean.height = part.image_url.height;\n return { ...part, image_url: clean };\n }\n // 清洗 text part 中的非法 Unicode 字符\n if (part && part.type === 'text' && typeof part.text === 'string') {\n return { ...part, text: sanitizeContentForApi(part.text) };\n }\n return part;\n });\n }\n\n // 清洗 content 字符串中的非法 Unicode(如孤立的代理对字符)\n // 防止 substring 截断 emoji 等原因产生的非法字符导致 API 服务端 JSON 解析失败\n if (typeof result.content === 'string') {\n result.content = sanitizeContentForApi(result.content);\n }\n\n if (result.role === 'tool') {\n if (!result.tool_call_id) {\n logger.warn(`[Background] foundmessage ${index} missing tool_call_id,skipped`, msg);\n return null;\n }\n return result;\n }\n\n if (result.role === 'assistant' && Array.isArray(result.tool_calls)) {\n result.tool_calls = result.tool_calls.map(tc => ({\n id: tc.id,\n type: tc.type,\n function: tc.function\n }));\n } else if (result.role === 'assistant' && result.tool_calls !== undefined) {\n logger.warn(`[Background] foundmessage ${index} tool_calls non-group (type: ${typeof result.tool_calls}),cleared`);\n delete result.tool_calls;\n }\n\n if (result.role === 'assistant' && result.content === null) {\n result.content = '';\n }\n\n return result;\n }).filter(msg => msg !== null);\n\n // 扫描并修复 tool_calls/tool 配对:验证 tool_call_id 与 tool_calls[].id 严格匹配\n for (let i = 0; i < filtered.length; i++) {\n const msg = filtered[i];\n if (msg?.role === 'assistant' && msg.tool_calls) {\n // 收集该 assistant 之后连续出现的 tool 消息的 tool_call_id\n const followingToolIds = new Set();\n for (let j = i + 1; j < filtered.length; j++) {\n if (filtered[j].role === 'tool') {\n followingToolIds.add(filtered[j].tool_call_id);\n } else {\n break;\n }\n }\n\n // 检查 assistant.tool_calls 中的每个 id 是否都有对应的 tool 响应\n const matchedCalls = msg.tool_calls.filter(tc => followingToolIds.has(tc.id));\n\n if (matchedCalls.length === 0) {\n // 完全没有匹配的 tool 响应 → 清除所有 tool_calls\n logger.warn('[Background] filterApiMessages: ', i, ' assistant message tool_calls no matchconfig tool response,cleared');\n delete msg.tool_calls;\n if (!msg.content) {\n filtered.splice(i, 1);\n i--;\n }\n } else if (matchedCalls.length < msg.tool_calls.length) {\n // 部分匹配 → 只保留有响应的 tool_calls\n const unmatched = msg.tool_calls.filter(tc => !followingToolIds.has(tc.id));\n logger.warn('[Background] filterApiMessages: ', i, ' assistant message', unmatched.length, ' tool_call no corresponding response,removed,keep', matchedCalls.length, '');\n msg.tool_calls = matchedCalls;\n }\n }\n }\n\n return filtered;\n}\n\n/**\n * 清理 image_url 中的额外字段,仅保留标准 url 字段\n * 在实际构建 API 请求体时调用,移除 width/height 等所有非标准字段\n * 返回浅拷贝新数组,不修改原数组,不影响后续 token 估算和裁剪逻辑\n * @param {Array} messages - 消息数组\n * @returns {Array} 清理后的消息数组\n */\nexport function sanitizeImageUrlsForApi(messages) {\n if (!messages || messages.length === 0) return messages;\n return messages.map(msg => {\n if (!msg || !Array.isArray(msg.content)) return msg;\n let contentChanged = false;\n const newContent = msg.content.map(part => {\n if (part && part.type === 'image_url' && part.image_url) {\n contentChanged = true;\n return { type: 'image_url', image_url: { url: part.image_url.url } };\n }\n return part;\n });\n return contentChanged ? { ...msg, content: newContent } : msg;\n });\n}\n\n/**\n * 从消息 content 中移除图片数据,仅保留文本部分\n * 用于发送历史消息时避免携带已无用的 Base64 图片\n * @param {string|Array} content - 消息内容\n * @returns {string|Array} 仅含文本的内容\n */\nexport function stripImagesFromContent(content) {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n const textParts = content.filter(c => c.type === 'text');\n return textParts.length === 1 ? textParts[0].text : textParts;\n }\n return content;\n}\n\n/**\n * 从消息 content 中提取纯文本字符串\n * 用于日志、摘要、预筛选、反思等\"把 content 当文本用\"的场景,\n * 避免把含 Base64 图片的数组 content 直接 JSON.stringify 导致图片数据污染文本\n * - 字符串:原样返回\n * - 数组(多模态):拼接所有 text part,忽略 image_url 等非文本部分\n * - null/undefined:返回空字符串\n * - 其他对象:JSON.stringify 兜底\n * @param {string|Array|*} content\n * @returns {string}\n */\nexport function extractTextFromContent(content) {\n if (content == null) return '';\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .filter(part => part && part.type === 'text' && part.text)\n .map(part => part.text)\n .join('\\n');\n }\n return JSON.stringify(content);\n}\n\n// ============================================================\n// 引用内容压缩\n// ============================================================\n\n// 引用内容保留的最大 token 数(超过此值的引用将压缩)\nconst MAX_QUOTED_CONTEXT_TOKENS = 2000;\n\n/**\n * 压缩引用/选中内容,防止大段内容永久占据上下文\n * @param {string} ctx - 原始引用内容\n * @returns {{ compressed: string, wasCompressed: boolean }}\n */\nexport function compressQuotedContext(ctx) {\n if (!ctx) return { compressed: ctx, wasCompressed: false };\n const tokens = estimateTokens(ctx);\n if (tokens <= MAX_QUOTED_CONTEXT_TOKENS) {\n return { compressed: ctx, wasCompressed: false };\n }\n const truncated = truncateByTokens(ctx, MAX_QUOTED_CONTEXT_TOKENS);\n logger.debug(`[TokenCounter] referenced contentcompress: ${tokens} → ${estimateTokens(truncated)} tokens`);\n return { compressed: truncated, wasCompressed: true };\n}\n","import logger from '../shared/logger.js';\nimport { t, registerTranslations } from '../shared/i18n.js';\n\n// storage/db.js - IndexedDB 封装层\n// 提供 Promise 化的 IndexedDB 操作,支持 side panel 和 service worker 共享访问\n\nregisterTranslations('zh', {\n db: {\n sessionLabel: '会话 {id}',\n archivedLabel: '[归档] {label}',\n },\n});\n\nregisterTranslations('en', {\n db: {\n sessionLabel: 'Session {id}',\n archivedLabel: '[Archived] {label}',\n },\n});\n\nconst DB_NAME = 'ai-helper-db';\nconst DB_VERSION = 6;\n\n// ReAct Checkpoint TTL:7 天\nexport const REACT_CHECKPOINT_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n// chrome.storage.local 中 checkpoint 的 key 前缀(后备存储)\n// 当 IndexedDB 的 reactCheckpoints store 不可用时,使用 chrome.storage.local 作为可靠后备\nconst CHECKPOINT_KEY_PREFIX = 'react_checkpoint_';\n\n/**\n * 获取数据库连接(单例)\n * @returns {Promise<IDBDatabase>}\n */\nfunction openDB() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onupgradeneeded = (event) => {\n const db = event.target.result;\n\n // 活跃会话存储(keyPath: id)\n if (!db.objectStoreNames.contains('sessions')) {\n const sessionsStore = db.createObjectStore('sessions', { keyPath: 'id' });\n sessionsStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n }\n\n // 活跃会话 ID(单条记录,keyPath: key)\n if (!db.objectStoreNames.contains('activeSession')) {\n db.createObjectStore('activeSession', { keyPath: 'key' });\n }\n\n // 归档会话存储(keyPath: id)\n if (!db.objectStoreNames.contains('archivedSessions')) {\n const archiveStore = db.createObjectStore('archivedSessions', { keyPath: 'id' });\n archiveStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n\n // UI 原型存储(keyPath: id)\n if (!db.objectStoreNames.contains('uiPrototypes')) {\n const prototypeStore = db.createObjectStore('uiPrototypes', { keyPath: 'id' });\n prototypeStore.createIndex('createdAt', 'createdAt', { unique: false });\n prototypeStore.createIndex('sessionId', 'sessionId', { unique: false });\n }\n\n // Token 使用统计存储(keyPath: id)\n if (!db.objectStoreNames.contains('tokenStats')) {\n const tokenStatsStore = db.createObjectStore('tokenStats', { keyPath: 'id' });\n tokenStatsStore.createIndex('sessionId', 'sessionId', { unique: false });\n tokenStatsStore.createIndex('timestamp', 'timestamp', { unique: false });\n }\n\n // ReAct Checkpoint 存储(keyPath: sessionId)—— 用于任务中断后续接\n if (!db.objectStoreNames.contains('reactCheckpoints')) {\n const checkpointStore = db.createObjectStore('reactCheckpoints', { keyPath: 'sessionId' });\n checkpointStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n }\n\n // 消息收藏存储(keyPath: id)\n if (!db.objectStoreNames.contains('bookmarks')) {\n const bookmarksStore = db.createObjectStore('bookmarks', { keyPath: 'id' });\n bookmarksStore.createIndex('sessionId', 'sessionId', { unique: false });\n bookmarksStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n\n // 定时任务存储(keyPath: id)\n if (!db.objectStoreNames.contains('scheduledTasks')) {\n const tasksStore = db.createObjectStore('scheduledTasks', { keyPath: 'id' });\n tasksStore.createIndex('nextRunAt', 'nextRunAt', { unique: false });\n tasksStore.createIndex('enabled', 'enabled', { unique: false });\n }\n };\n\n request.onsuccess = (event) => {\n const db = event.target.result;\n\n // 关键:当其他连接(如 side_panel 持有的旧连接)触发版本变更时,\n // 主动关闭当前连接,避免阻塞 DB 升级。\n // 没有此处理会导致 SW 升级 DB 时被旧连接阻塞,reactCheckpoints store 无法创建,\n // 表现为 checkpoint 写入静默失败 → 恢复时\"未找到 checkpoint\"。\n db.onversionchange = (event) => {\n logger.debug('[IDB] detected versionchange event,maindynamicclosecurrent connection as allows upgrade');\n try { event.target.close(); } catch (e) {}\n // 重置单例缓存,下次 getDB() 会重新打开\n dbPromise = null;\n };\n\n resolve(db);\n };\n\n request.onerror = (event) => {\n logger.error('[IDB] opendatabase failed:', event.target.error);\n reject(event.target.error);\n };\n\n // 处理被阻塞的升级(其他连接持有旧版本时触发)\n request.onblocked = () => {\n logger.warn('[IDB] databaseupgrade blocked (other connections notclose),waitingretry...');\n // 不直接 reject,让调用方稍后重试 getDB() 时自然获得新连接\n };\n });\n}\n\nlet dbPromise = null;\n\n/**\n * 重置数据库连接缓存(用于 Service Worker 重启后恢复)\n */\nfunction resetDBConnection() {\n if (dbPromise) {\n dbPromise = null;\n logger.debug('[IDB] databaseconnectionreset');\n }\n}\n\n/**\n * 获取数据库实例(缓存单例)\n */\nexport function getDB() {\n if (!dbPromise) {\n dbPromise = openDB();\n }\n return dbPromise;\n}\n\n/**\n * 通用事务包装器(带连接恢复重试)\n * @param {string} storeName - objectStore 名称\n * @param {'readonly'|'readwrite'} mode\n * @param {function(IDBObjectStore, function): void} callback\n * @param {boolean} [isRetry=false] - 是否为重试调用(内部使用)\n * @returns {Promise<any>}\n */\nexport function withStore(storeName, mode, callback, isRetry = false) {\n return getDB().then((db) => {\n return new Promise((resolve, reject) => {\n let resolvedValue = null;\n let settled = false;\n\n // 检查 store 是否存在;不存在时强制重置连接并重试(触发 DB 升级创建缺失的 store)\n // 这是 \"reactCheckpoints store 不存在导致 checkpoint 静默失败\" 的根因修复\n if (!db.objectStoreNames.contains(storeName)) {\n logger.error(`[IDB] store \"${storeName}\" not found!current DB version: ${db.version}, store column table: [${Array.from(db.objectStoreNames).join(', ')}]`);\n if (!isRetry) {\n settled = true;\n // 强制重置连接,下次 getDB() 会重新打开并触发 onupgradeneeded(如果版本有变化)\n // 但如果 DB 已经是最新版本但 store 仍然缺失(连接陈旧),需要先关闭再重开\n try { db.close(); } catch (e) {}\n resetDBConnection();\n // 延迟重试,给 DB 重开一点时间\n setTimeout(() => {\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n }, 50);\n return;\n } else {\n settled = true;\n reject(new Error(`Store \"${storeName}\" does not exist in database (version ${db.version})`));\n return;\n }\n }\n\n let tx;\n try {\n tx = db.transaction(storeName, mode);\n } catch (e) {\n // transaction() 可能抛出同步异常(如连接已关闭)\n logger.error(`[IDB] create transaction failed (${storeName}):`, e.message);\n if (!isRetry) {\n settled = true;\n resetDBConnection();\n setTimeout(() => {\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n }, 50);\n return;\n } else {\n settled = true;\n reject(e);\n return;\n }\n }\n const store = tx.objectStore(storeName);\n\n // 回调中的 resolve 仅保存结果,实际 resolve 在 tx.oncomplete 中执行\n // 确保事务已提交后再返回,防止调用方读取到未持久化的数据\n callback(store, (result) => {\n resolvedValue = result;\n });\n\n tx.oncomplete = () => {\n if (!settled) {\n settled = true;\n resolve(resolvedValue);\n }\n };\n\n tx.onerror = (event) => {\n if (settled) return;\n settled = true;\n const err = event.target.error || new Error(`Transaction error on ${storeName}`);\n logger.error(`[IDB] transaction error (${storeName}):`, err);\n\n if (!isRetry) {\n // Service Worker 重启后连接可能已关闭,重置并重试一次\n resetDBConnection();\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n } else {\n reject(err);\n }\n };\n\n tx.onabort = () => {\n if (settled) return;\n settled = true;\n\n if (!isRetry) {\n resetDBConnection();\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n } else {\n reject(new Error(`Transaction aborted on ${storeName}`));\n }\n };\n });\n });\n}\n\n// ==================== Sessions CRUD ====================\n\n/**\n * 获取所有活跃会话\n */\nexport function getAllSessions() {\n return withStore('sessions', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 获取单个会话\n */\nexport function getSession(id) {\n return withStore('sessions', 'readonly', (store, resolve) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 从会话的 messageHistory 中直接删除指定消息(不依赖 saveCurrentSession 的完整保存流程)\n * @param {string} sessionId\n * @param {string} messageId\n * @returns {Promise<boolean>}\n */\nexport async function deleteMessageFromSession(sessionId, messageId) {\n if (!sessionId || !messageId) return false;\n try {\n const session = await getSession(sessionId);\n if (!session || !session.messageHistory) return false;\n\n const idx = session.messageHistory.findIndex(\n msg => (msg.messageId || msg.id) === messageId\n );\n if (idx === -1) return false;\n\n session.messageHistory.splice(idx, 1);\n return await putSession(session);\n } catch (e) {\n logger.error('[DB] deleteMessageFromSession failed:', e);\n return false;\n }\n}\n\n/**\n * 保存/更新会话(put = insert or update)\n */\nexport function putSession(session) {\n return withStore('sessions', 'readwrite', (store, resolve) => {\n const request = store.put(session);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除会话\n */\nexport function deleteSession(id) {\n return withStore('sessions', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 批量保存/更新多个会话\n */\nexport function putSessions(sessions) {\n return getDB().then((db) => {\n return new Promise((resolve) => {\n const tx = db.transaction('sessions', 'readwrite');\n const store = tx.objectStore('sessions');\n sessions.forEach((s) => store.put(s));\n tx.oncomplete = () => resolve(true);\n tx.onerror = () => resolve(false);\n });\n });\n}\n\n// ==================== Active Session ID ====================\n\n/**\n * 获取当前活跃会话 ID\n */\nexport function getActiveSessionId() {\n return withStore('activeSession', 'readonly', (store, resolve) => {\n const request = store.get('active');\n request.onsuccess = () => resolve(request.result?.sessionId || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 设置当前活跃会话 ID\n */\nexport function setActiveSessionId(sessionId) {\n return withStore('activeSession', 'readwrite', (store, resolve) => {\n const request = store.put({ key: 'active', sessionId });\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n// ==================== Archived Sessions CRUD ====================\n\n/**\n * 获取所有归档会话\n */\nexport function getAllArchivedSessions() {\n return withStore('archivedSessions', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 保存归档会话\n */\nexport function putArchivedSession(session) {\n return withStore('archivedSessions', 'readwrite', (store, resolve) => {\n const request = store.put(session);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除归档会话\n */\nexport function deleteArchivedSession(id) {\n return withStore('archivedSessions', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 清空并批量写入归档会话(用于截断超出 20 条的场景)\n */\nexport function replaceArchivedSessions(sessions) {\n return getDB().then((db) => {\n return new Promise((resolve) => {\n const tx = db.transaction('archivedSessions', 'readwrite');\n const store = tx.objectStore('archivedSessions');\n\n // 清空\n const clearReq = store.clear();\n clearReq.onsuccess = () => {\n // 批量写入\n sessions.forEach((s) => store.put(s));\n };\n tx.oncomplete = () => resolve(true);\n tx.onerror = () => resolve(false);\n });\n });\n}\n\n// ==================== UI Prototypes CRUD ====================\n\n/**\n * 保存 UI 原型\n */\nexport function saveUiPrototype(prototype) {\n const now = Date.now();\n const data = {\n ...prototype,\n createdAt: prototype.createdAt || now,\n updatedAt: now\n };\n return withStore('uiPrototypes', 'readwrite', (store, resolve) => {\n const request = store.put(data);\n request.onsuccess = () => resolve(data);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取单个 UI 原型\n */\nexport function getUiPrototype(id) {\n return withStore('uiPrototypes', 'readonly', (store, resolve) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取所有 UI 原型(按创建时间倒序)\n */\nexport function listUiPrototypes(sessionId = null) {\n return withStore('uiPrototypes', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => {\n let results = request.result || [];\n if (sessionId) {\n results = results.filter(p => p.sessionId === sessionId);\n }\n results.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));\n resolve(results);\n };\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 删除 UI 原型\n */\nexport function deleteUiPrototype(id) {\n return withStore('uiPrototypes', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n// ==================== 搜索 ====================\n\n/**\n * 从所有活跃会话中搜索消息\n * @param {string} sessionId - 限定会话 ID,null 表示搜索所有活跃会话\n * @returns {Promise<Array<{session: string, index: number, role: string, content: string}>>}\n */\nexport function searchActiveSessionsMessages(sessionId) {\n if (sessionId) {\n // 指定 sessionId 时使用 O(1) 主键查询,避免全量加载所有会话\n return getSession(sessionId).then((session) => {\n if (!session) return [];\n const label = session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) });\n return (session.messageHistory || []).filter(msg => msg.content).map((msg, idx) => ({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n }));\n });\n }\n return getAllSessions().then((all) => {\n const sessions = all;\n const results = [];\n sessions.forEach((session) => {\n const label = session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) });\n (session.messageHistory || []).forEach((msg, idx) => {\n if (msg.content) {\n results.push({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n });\n }\n });\n });\n return results;\n });\n}\n\n/**\n * 获取所有归档会话的消息\n * @returns {Promise<Array<{session: string, index: number, role: string, content: string}>>}\n */\nexport function getArchivedSessionsMessages() {\n return getAllArchivedSessions().then((archiveSessions) => {\n const results = [];\n archiveSessions.forEach((session) => {\n const label = t('db.archivedLabel', { label: session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) }) });\n (session.messages || []).forEach((msg, idx) => {\n if (msg.content) {\n results.push({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n });\n }\n });\n });\n return results;\n });\n}\n\n// ==================== 数据迁移 ====================\n\n/**\n * 从 chrome.storage.local 迁移数据到 IndexedDB\n * 迁移后清除 chrome.storage.local 中的大数据 key\n * @returns {Promise<boolean>}\n */\nexport function migrateFromChromeStorage() {\n return new Promise((resolve) => {\n chrome.storage.local.get(['sessions', 'conversationSessions'], async (result) => {\n let migrated = false;\n\n // 迁移活跃会话\n if (result.sessions?.list?.length > 0) {\n const { list, activeSessionId } = result.sessions;\n await putSessions(list);\n if (activeSessionId) {\n await setActiveSessionId(activeSessionId);\n }\n migrated = true;\n logger.debug('[IDB] migrated activesession:', list.length, '');\n }\n\n // 迁移归档会话\n if (result.conversationSessions?.sessions?.length > 0) {\n await replaceArchivedSessions(result.conversationSessions.sessions);\n migrated = true;\n logger.debug('[IDB] migrated archivesession:', result.conversationSessions.sessions.length, '');\n }\n\n if (migrated) {\n // 清除 chrome.storage 中的大数据 key,保留其他配置\n chrome.storage.local.remove(['sessions', 'conversationSessions', 'chatHistory'], () => {\n logger.debug('[IDB] cleaned chrome.storage.local olddata');\n });\n }\n\n resolve(migrated);\n });\n });\n}\n\n/**\n * 检查 IndexedDB 是否有数据,没有则尝试从 chrome.storage 迁移\n * @returns {Promise<void>}\n */\nexport async function ensureMigration() {\n const sessions = await getAllSessions();\n if (sessions.length > 0) return; // 已有数据\n\n const activeId = await getActiveSessionId();\n if (activeId) return; // 已有活跃会话标记\n\n // 尝试迁移\n await migrateFromChromeStorage();\n}\n\n// ==================== React Checkpoints CRUD ====================\n//\n// checkpoint 数据结构:\n// {\n// sessionId: string, // 主键\n// currentMessages: Array, // 完整的 API 消息数组(含 tool_calls/tool 消息对)\n// executionLog: Array, // 执行日志\n// iteration: number, // 当前迭代次数\n// globalIteration: { value }, // 全局迭代计数\n// subtaskPlan: Object|null, // 任务拆解计划\n// currentSubtaskIndex: number|null, // 当前子任务索引\n// totalReflectionRounds: number, // 反思轮数\n// model: string, // 使用的模型\n// apiParams: Object, // API 参数\n// taskContext: Object|null, // 任务上下文(子任务/子代理)\n// tabId: number|null, // 标签页 ID\n// callId: string|null, // 调用 ID\n// interruptedReason: string, // 中断原因\n// createdAt: number, // 创建时间戳\n// updatedAt: number, // 更新时间戳\n// }\n\n/**\n * 保存/更新 ReAct checkpoint\n * 采用双写策略:IndexedDB + chrome.storage.local(后备)\n * 当 IndexedDB 的 reactCheckpoints store 不存在或写入失败时,chrome.storage.local 确保数据不丢失\n * @param {Object} checkpoint - 必须包含 sessionId\n * @returns {Promise<boolean>}\n */\nexport async function saveReactCheckpoint(checkpoint) {\n if (!checkpoint?.sessionId) return false;\n const now = Date.now();\n const data = {\n ...checkpoint,\n updatedAt: now,\n createdAt: checkpoint.createdAt || now,\n };\n const key = CHECKPOINT_KEY_PREFIX + checkpoint.sessionId;\n\n // 策略:先写 chrome.storage.local(可靠),再尝试 IndexedDB(性能优化)\n // 这样即使 IndexedDB 完全不可用,checkpoint 也不会丢失\n let storageOk = false;\n try {\n await chrome.storage.local.set({ [key]: data });\n storageOk = true;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local save checkpoint failed:', e.message);\n }\n\n // 同时写入 IndexedDB(如果可用),供 getAllReactCheckpoints 高效查询\n let idbOk = false;\n try {\n idbOk = await withStore('reactCheckpoints', 'readwrite', (store, resolve) => {\n const request = store.put(data);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n } catch (e) {\n // IndexedDB 不可用(store 不存在等),不影响主流程\n logger.warn('[Storage] IndexedDB save checkpoint failed (downgraded to chrome.storage.local):', e.message);\n }\n\n if (!storageOk && !idbOk) {\n logger.error('[Storage] checkpoint save complete failed:chrome.storage.local and IndexedDB averagenon- with ');\n return false;\n }\n return true;\n}\n\n/**\n * 获取指定会话的 ReAct checkpoint\n * 优先从 IndexedDB 读取,不存在时降级到 chrome.storage.local\n * @param {string} sessionId\n * @returns {Promise<Object|null>}\n */\nexport async function getReactCheckpoint(sessionId) {\n if (!sessionId) return null;\n\n // 优先尝试 IndexedDB\n try {\n const result = await withStore('reactCheckpoints', 'readonly', (store, resolve) => {\n const request = store.get(sessionId);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n if (result) return result;\n } catch (e) {\n // IndexedDB 不可用,继续尝试 chrome.storage.local\n }\n\n // 降级:从 chrome.storage.local 读取\n try {\n const key = CHECKPOINT_KEY_PREFIX + sessionId;\n const data = await chrome.storage.local.get(key);\n return data[key] || null;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local read checkpoint failed:', e.message);\n return null;\n }\n}\n\n/**\n * 删除指定会话的 ReAct checkpoint\n * 同时从 IndexedDB 和 chrome.storage.local 中删除\n * @param {string} sessionId\n * @returns {Promise<boolean>}\n */\nexport async function deleteReactCheckpoint(sessionId) {\n if (!sessionId) return false;\n let ok = false;\n\n // 从 chrome.storage.local 删除\n try {\n const key = CHECKPOINT_KEY_PREFIX + sessionId;\n await chrome.storage.local.remove(key);\n ok = true;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local delete checkpoint failed:', e.message);\n }\n\n // 从 IndexedDB 删除\n try {\n await withStore('reactCheckpoints', 'readwrite', (store, resolve) => {\n const request = store.delete(sessionId);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n ok = true;\n } catch (e) {\n // IndexedDB 不可用,不影响\n }\n\n return ok;\n}\n\n/**\n * 获取所有 ReAct checkpoint(用于 TTL 清理和诊断)\n * 合并 IndexedDB 和 chrome.storage.local 中的结果\n * @returns {Promise<Array>}\n */\nexport async function getAllReactCheckpoints() {\n // 从 chrome.storage.local 获取所有 checkpoint\n let storageCheckpoints = [];\n try {\n const all = await chrome.storage.local.get(null);\n for (const [key, value] of Object.entries(all)) {\n if (key.startsWith(CHECKPOINT_KEY_PREFIX) && value?.sessionId) {\n storageCheckpoints.push(value);\n }\n }\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local get all checkpoint failed:', e.message);\n }\n\n // 从 IndexedDB 获取\n let idbCheckpoints = [];\n try {\n idbCheckpoints = await withStore('reactCheckpoints', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n } catch (e) {\n // IndexedDB 不可用\n }\n\n // 合并去重(以 sessionId 为键,IndexedDB 优先)\n const merged = new Map();\n for (const cp of storageCheckpoints) {\n merged.set(cp.sessionId, cp);\n }\n for (const cp of idbCheckpoints) {\n merged.set(cp.sessionId, cp);\n }\n return Array.from(merged.values());\n}\n\n/**\n * 清理过期的 ReAct checkpoint(TTL: 7 天)\n * 应在 SW 启动时调用\n * @returns {Promise<number>} 清理的条数\n */\nexport async function cleanupExpiredReactCheckpoints() {\n const all = await getAllReactCheckpoints();\n const now = Date.now();\n const expired = all.filter(c => (c.updatedAt || 0) < now - REACT_CHECKPOINT_TTL_MS);\n if (expired.length === 0) return 0;\n\n let deleted = 0;\n for (const cp of expired) {\n const ok = await deleteReactCheckpoint(cp.sessionId);\n if (ok) deleted++;\n }\n logger.debug(`[IDB] cleanup ${deleted} expired ReAct checkpoint`);\n return deleted;\n}\n\n// ==================== Bookmarks CRUD ====================\n\n/**\n * 获取所有收藏\n * @returns {Promise<Array>}\n */\nexport function getAllBookmarks() {\n return withStore('bookmarks', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 保存收藏\n * @param {Object} bookmark - { id, sessionId, messageId, content, sessionTitle, createdAt, pinned }\n * @returns {Promise<boolean>}\n */\nexport function putBookmark(bookmark) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const request = store.put(bookmark);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除收藏\n * @param {string} id\n * @returns {Promise<boolean>}\n */\nexport function deleteBookmark(id) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 更新收藏的置顶状态\n * @param {string} id\n * @param {boolean} pinned\n * @returns {Promise<boolean>}\n */\nexport function updateBookmarkPin(id, pinned) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const getReq = store.get(id);\n getReq.onsuccess = () => {\n const bm = getReq.result;\n if (!bm) { resolve(false); return; }\n bm.pinned = pinned;\n const putReq = store.put(bm);\n putReq.onsuccess = () => resolve(true);\n putReq.onerror = () => resolve(false);\n };\n getReq.onerror = () => resolve(false);\n });\n}\n\n// ==================== Scheduled Tasks CRUD ====================\n\n/**\n * 获取单个定时任务\n */\nexport function getScheduledTask(id) {\n return withStore('scheduledTasks', 'readonly', (store, resolve) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取所有定时任务\n */\nexport function getAllScheduledTasks() {\n return withStore('scheduledTasks', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 保存/更新定时任务(put = insert or update)\n */\nexport function putScheduledTask(task) {\n return withStore('scheduledTasks', 'readwrite', (store, resolve) => {\n const request = store.put(task);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除定时任务\n */\nexport function deleteScheduledTask(id) {\n return withStore('scheduledTasks', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n","// storage/token-store.js - Token 使用统计的 IndexedDB CRUD\n\nimport { getDB, withStore } from './db.js';\n\nconst STORE_NAME = 'tokenStats';\n\n// 全局汇总存储 key\nconst OVERALL_KEY = 'overall';\n\n// 系统级会话 ID:不计入\"总会话数\",但其 Token 消耗仍计入总量\nconst SYSTEM_SESSION_IDS = new Set(['selection_toolbar']);\n\n/**\n * 记录单次 API 调用的 token 使用\n * @param {Object} record\n * @param {string} record.sessionId\n * @param {string} record.model\n * @param {number} record.promptTokens\n * @param {number} record.completionTokens\n * @param {number} record.totalTokens\n * @param {number} record.contextWindow\n * @param {string} record.callType - 'react_loop' | 'non_stream' | 'reflection' | 'tool_reflection' | 'subtask_reflection'\n */\nexport function recordTokenCall(record) {\n const entry = {\n id: `tcr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n sessionId: record.sessionId,\n timestamp: new Date().toISOString(),\n model: record.model || 'unknown',\n promptTokens: record.promptTokens || 0,\n completionTokens: record.completionTokens || 0,\n totalTokens: record.totalTokens || 0,\n contextWindow: record.contextWindow || 256000,\n contextUsageRate: record.contextWindow > 0\n ? ((record.promptTokens || 0) / record.contextWindow)\n : 0,\n callType: record.callType || 'unknown'\n };\n\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.put(entry);\n req.onsuccess = () => resolve(true);\n req.onerror = () => resolve(false);\n });\n}\n\n/**\n * 获取某个会话的 token 汇总\n * @param {string} sessionId\n * @returns {Promise<Object>}\n */\nexport function getSessionTokenSummary(sessionId) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n // 使用 sessionId 索引 + key range 缩小候选集(精确匹配 + 子任务前缀 sessionId_xxx),\n // 避免 getAll() 全量加载所有会话的记录\n const index = store.index('sessionId');\n const req = sessionId\n ? index.getAll(IDBKeyRange.bound(sessionId, sessionId + '\\uffff'))\n : store.getAll();\n req.onsuccess = () => {\n // 仍需精确过滤,确保语义与原实现一致(排除 sessionIdX 类前缀误匹配)\n const records = (req.result || []).filter(r => \n r.sessionId === sessionId || \n (sessionId && r.sessionId?.startsWith(sessionId + '_'))\n );\n if (records.length === 0) {\n resolve({\n sessionId,\n totalPromptTokens: 0,\n totalCompletionTokens: 0,\n totalTokens: 0,\n apiCallCount: 0,\n avgContextUsageRate: 0,\n maxContextUsageRate: 0,\n minContextUsageRate: 0,\n records: []\n });\n return;\n }\n\n const usageRates = records.map(r => r.contextUsageRate || 0).filter(r => r > 0);\n resolve({\n sessionId,\n totalPromptTokens: records.reduce((s, r) => s + (r.promptTokens || 0), 0),\n totalCompletionTokens: records.reduce((s, r) => s + (r.completionTokens || 0), 0),\n totalTokens: records.reduce((s, r) => s + (r.totalTokens || 0), 0),\n apiCallCount: records.length,\n avgContextUsageRate: usageRates.length > 0\n ? usageRates.reduce((s, r) => s + r, 0) / usageRates.length\n : 0,\n maxContextUsageRate: usageRates.length > 0 ? Math.max(...usageRates) : 0,\n minContextUsageRate: usageRates.length > 0 ? Math.min(...usageRates) : 0,\n records: records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)).slice(0, 20)\n });\n };\n req.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取全局 token 汇总\n * @returns {Promise<Object>}\n */\nexport function getOverallTokenSummary() {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n if (records.length === 0) {\n resolve({\n totalPromptTokens: 0,\n totalCompletionTokens: 0,\n totalTokens: 0,\n totalApiCalls: 0,\n totalSessions: 0,\n lastUpdated: null\n });\n return;\n }\n\n const sessionIds = new Set(\n records.map(r => r.sessionId).filter(id => id && !SYSTEM_SESSION_IDS.has(id))\n );\n resolve({\n totalPromptTokens: records.reduce((s, r) => s + (r.promptTokens || 0), 0),\n totalCompletionTokens: records.reduce((s, r) => s + (r.completionTokens || 0), 0),\n totalTokens: records.reduce((s, r) => s + (r.totalTokens || 0), 0),\n totalApiCalls: records.length,\n totalSessions: sessionIds.size,\n lastUpdated: records.reduce((latest, r) => {\n return r.timestamp > latest ? r.timestamp : latest;\n }, '')\n });\n };\n req.onerror = () => resolve(null);\n });\n}\n\n/**\n * 清除某个会话的 token 统计\n * @param {string} sessionId\n */\nexport function clearSessionTokenStats(sessionId) {\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n const toDelete = records.filter(r => r.sessionId === sessionId);\n let deleted = 0;\n toDelete.forEach(r => {\n store.delete(r.id);\n deleted++;\n });\n resolve(deleted);\n };\n req.onerror = () => resolve(0);\n });\n}\n\n/**\n * 清除所有 token 统计\n */\nexport function clearAllTokenStats() {\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.clear();\n req.onsuccess = () => resolve(true);\n req.onerror = () => resolve(false);\n });\n}\n\n/**\n * 获取最近 N 条 token 记录(全局)\n * @param {number} limit\n */\nexport function getRecentTokenRecords(limit = 20) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));\n resolve(records.slice(0, limit));\n };\n req.onerror = () => resolve([]);\n });\n}\n\n// ==================== 阶段二:新增聚合查询接口 ====================\n\n/**\n * 获取最近 N 天的每日 token 聚合\n * @param {number} days - 天数(默认 30)\n * @returns {Promise<Array<{date: string, totalTokens: number, apiCalls: number}>>}\n */\nexport function getDailyTokenSummary(days = 30) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const now = new Date();\n const startDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);\n // 使用 timestamp 索引下界,只读取区间内记录(ISO 字符串可按字典序比较)\n const index = store.index('timestamp');\n const req = index.getAll(IDBKeyRange.lowerBound(startDate.toISOString()));\n req.onsuccess = () => {\n const records = req.result || [];\n \n // 按天聚合\n const dailyMap = new Map();\n records.forEach(r => {\n const recordDate = new Date(r.timestamp);\n if (recordDate < startDate) return;\n \n const dateKey = recordDate.toISOString().split('T')[0]; // YYYY-MM-DD\n if (!dailyMap.has(dateKey)) {\n dailyMap.set(dateKey, { date: dateKey, totalTokens: 0, apiCalls: 0 });\n }\n const dayData = dailyMap.get(dateKey);\n dayData.totalTokens += r.totalTokens || 0;\n dayData.apiCalls += 1;\n });\n \n // 转换为数组并按日期排序\n const result = Array.from(dailyMap.values()).sort((a, b) => a.date.localeCompare(b.date));\n resolve(result);\n };\n req.onerror = () => resolve([]);\n });\n}\n\n/**\n * 按时间范围过滤的全局汇总\n * @param {string} startDate - ISO 日期字符串\n * @param {string} endDate - ISO 日期字符串\n * @returns {Promise<Object>} 同 getOverallTokenSummary 结构\n */\nexport function getOverallTokenSummaryByRange(startDate, endDate) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n // 使用 timestamp 索引限定区间,只读取范围内的记录\n const index = store.index('timestamp');\n const req = index.getAll(IDBKeyRange.bound(startDate, endDate));\n req.onsuccess = () => {\n const records = req.result || [];\n const start = new Date(startDate);\n const end = new Date(endDate);\n \n // 二次精确过滤(防御 ISO 格式差异导致的字典序偏差)\n const filtered = records.filter(r => {\n const recordDate = new Date(r.timestamp);\n return recordDate >= start && recordDate <= end;\n });\n \n if (filtered.length === 0) {\n resolve({\n totalPromptTokens: 0,\n totalCompletionTokens: 0,\n totalTokens: 0,\n totalApiCalls: 0,\n totalSessions: 0,\n lastUpdated: null\n });\n return;\n }\n \n const sessionIds = new Set(\n filtered.map(r => r.sessionId).filter(id => id && !SYSTEM_SESSION_IDS.has(id))\n );\n resolve({\n totalPromptTokens: filtered.reduce((s, r) => s + (r.promptTokens || 0), 0),\n totalCompletionTokens: filtered.reduce((s, r) => s + (r.completionTokens || 0), 0),\n totalTokens: filtered.reduce((s, r) => s + (r.totalTokens || 0), 0),\n totalApiCalls: filtered.length,\n totalSessions: sessionIds.size,\n lastUpdated: filtered.reduce((latest, r) => {\n return r.timestamp > latest ? r.timestamp : latest;\n }, '')\n });\n };\n req.onerror = () => resolve(null);\n });\n}\n\n/**\n * 按模型聚合的 token 分布\n * @returns {Promise<Array<{model: string, totalTokens: number, percentage: number}>>}\n */\nexport function getTokenSummaryByModel() {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n const modelMap = new Map();\n \n records.forEach(r => {\n const model = r.model || 'unknown';\n if (!modelMap.has(model)) {\n modelMap.set(model, 0);\n }\n modelMap.set(model, modelMap.get(model) + (r.totalTokens || 0));\n });\n \n const totalTokens = Array.from(modelMap.values()).reduce((s, v) => s + v, 0);\n const result = Array.from(modelMap.entries())\n .map(([model, tokens]) => ({\n model,\n totalTokens: tokens,\n percentage: totalTokens > 0 ? (tokens / totalTokens) * 100 : 0\n }))\n .sort((a, b) => b.totalTokens - a.totalTokens);\n \n resolve(result);\n };\n req.onerror = () => resolve([]);\n });\n}\n\n/**\n * 按 callType 聚合的 token 分布\n * @returns {Promise<Array<{callType: string, totalTokens: number, percentage: number}>>}\n */\nexport function getTokenSummaryByCallType() {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n const callTypeMap = new Map();\n \n records.forEach(r => {\n const callType = r.callType || 'unknown';\n if (!callTypeMap.has(callType)) {\n callTypeMap.set(callType, 0);\n }\n callTypeMap.set(callType, callTypeMap.get(callType) + (r.totalTokens || 0));\n });\n \n const totalTokens = Array.from(callTypeMap.values()).reduce((s, v) => s + v, 0);\n const result = Array.from(callTypeMap.entries())\n .map(([callType, tokens]) => ({\n callType,\n totalTokens: tokens,\n percentage: totalTokens > 0 ? (tokens / totalTokens) * 100 : 0\n }))\n .sort((a, b) => b.totalTokens - a.totalTokens);\n \n resolve(result);\n };\n req.onerror = () => resolve([]);\n });\n}\n","// shared/agent-defaults.js - 内置 Agent 定义和模板\n\n/**\n * 内置默认 Agent(不可删除)\n */\nexport const BUILTIN_AGENTS = [\n {\n id: 'default',\n name: 'Default Assistant',\n description: 'All-purpose AI assistant with all tool capabilities',\n icon: '🤖',\n systemPrompt: null, // null = 使用全局 systemPrompt\n toolIds: null, // null = 使用全局 enabledTools\n skillIds: null, // null = 使用全部启用技能\n isBuiltin: true,\n allowSubDispatch: false,\n model: null,\n temperature: null,\n topP: null,\n }\n];\n\n/**\n * Agent 模板(供用户快速创建参考)\n */\nexport const AGENT_TEMPLATES = [\n {\n name: 'Code Review Expert',\n icon: '🔍',\n description: 'Focused on code review and quality assurance',\n systemPrompt: `You are a senior Code Review Expert. Your responsibilities:\n1. Review code for logical correctness, performance, and security\n2. Check code style, naming conventions, and best practices\n3. Identify potential bugs, memory leaks, and concurrency issues\n4. Provide actionable improvement suggestions with example code\n\nResponse principles:\n- Always indicate severity (critical/moderate/suggestion)\n- Provide specific code modification suggestions, avoid vague comments\n- Focus on maintainability, not just functional correctness`,\n systemPromptZh: `你是一位资深代码审查专家。你的职责:\n1. 审查代码的逻辑正确性、性能和安全性\n2. 检查代码风格、命名规范和最佳实践\n3. 识别潜在的 Bug、内存泄漏和并发问题\n4. 提供可操作的改进建议,并附带示例代码\n\n回复原则:\n- 始终标注严重程度(严重/中等/建议)\n- 提供具体的代码修改建议,避免模糊评论\n- 关注可维护性,而非仅关注功能正确性`,\n toolIds: ['page_content', 'search_browser_data', 'agent_file', 'agent_search', 'search_chats', 'fetch_url', 'plan_task'],\n allowSubDispatch: false,\n },\n {\n name: 'Web Automation Assistant',\n icon: '🌐',\n description: 'Focused on web interaction and automation',\n systemPrompt: `You are a web automation expert. Your strengths:\n1. Automate web page interactions based on user needs (clicking, form filling, scrolling, etc.)\n2. Extract and analyze web page content\n3. Handle multi-step web interaction flows\n\nOperating principles:\n- Understand page structure before acting; prefer query_elements first\n- Verify results after actions to ensure they take effect\n- Analyze errors and try alternative approaches\n- Never assume elements exist; fetch page info first when uncertain\n- For dialogs (alert/confirm/prompt), call handle_dialog BEFORE the action that triggers them, then use interact_element to proceed`,\n systemPromptZh: `你是一位网页自动化专家。你的优势:\n1. 根据用户需求自动化网页交互(点击、表单填写、滚动等)\n2. 提取和分析网页内容\n3. 处理多步骤的网页交互流程\n\n操作原则:\n- 行动前先理解页面结构,优先使用 query_elements\n- 操作后验证结果,确保生效\n- 分析错误并尝试替代方案\n- 不要假设元素存在,不确定时先获取页面信息\n- 涉及 alert/confirm/prompt 弹窗时,先在触发弹窗的操作前调用 handle_dialog,再通过 interact_element 继续`,\n toolIds: ['page_content', 'query_elements', 'interact_element', 'fill_form', 'scroll_to', 'wait_element', 'keyboard_input', 'select_dropdown', 'capture_page', 'extract_data', 'search_in_page', 'wait_navigation', 'scroll_collect', 'drag_drop', 'file_upload', 'iframe_content', 'manage_tab', 'handle_dialog'],\n allowSubDispatch: false,\n },\n {\n name: 'Data Analyst',\n icon: '📊',\n description: 'Focused on data extraction, analysis, and visualization',\n systemPrompt: `You are a data analyst. Your strengths:\n1. Extract structured data from web pages (tables, lists, JSON)\n2. Analyze and summarize data patterns\n3. Present analysis results in clear formats\n\nAnalysis principles:\n- Understand data structure before starting analysis\n- Use the most appropriate extraction method (tables → extract_data with dataType=table; links/images/forms → extract_data with dataType=links/images/forms; text/HTML → page_content with format=text/html)\n- Support analysis results with data; avoid subjective assumptions\n- Present conclusions using tables or charts`,\n systemPromptZh: `你是一位数据分析师。你的优势:\n1. 从网页中提取结构化数据(表格、列表、JSON)\n2. 分析和总结数据模式\n3. 以清晰格式呈现分析结果\n\n分析原则:\n- 开始分析前先理解数据结构\n- 使用最合适的提取方法(表格使用 extract_data 配合 dataType=table;链接/图片/表单等结构化数据使用 extract_data 配合 dataType=links/images/forms;纯文本/HTML 使用 page_content 配合 format=text/html)\n- 用数据支撑分析结果,避免主观臆断\n- 使用表格或图表呈现结论`,\n toolIds: ['page_content', 'extract_data', 'query_elements', 'search_in_page', 'scroll_collect', 'fetch_url', 'iframe_content', 'clipboard'],\n allowSubDispatch: false,\n },\n {\n name: 'Documentation Assistant',\n icon: '📝',\n description: 'Focused on technical documentation and content organization',\n systemPrompt: `You are a technical documentation expert. Your strengths:\n1. Write clear technical documentation (API docs, READMEs, user guides)\n2. Distill and summarize technical information\n3. Transform complex concepts into easy-to-understand documentation\n\nWriting principles:\n- Clear structure: Use headings, lists, and tables to organize content\n- Examples first: Key concepts must include code or configuration examples\n- Reader-oriented: Adjust technical depth based on target audience\n- Stay concise: Avoid redundancy; every paragraph should carry information`,\n systemPromptZh: `你是一位技术文档专家。你的优势:\n1. 编写清晰的技术文档(API 文档、README、用户指南)\n2. 提炼和总结技术信息\n3. 将复杂概念转化为易于理解的文档\n\n编写原则:\n- 结构清晰:使用标题、列表和表格组织内容\n- 示例先行:关键概念必须附带代码或配置示例\n- 面向读者:根据目标受众调整技术深度\n- 保持简洁:避免冗余,每段文字都应承载信息`,\n toolIds: ['page_content', 'clipboard', 'search_browser_data', 'search_chats', 'fetch_url', 'agent_file', 'agent_search', 'extract_data', 'capture_page', 'search_in_page'],\n allowSubDispatch: false,\n },\n];\n\n/**\n * 生成新的 Agent ID\n */\nexport function generateAgentId() {\n return 'agent_' + Date.now().toString(36) + '_' + Math.random().toString(36).substring(2, 6);\n}\n","// state.js - 全局状态变量\n// 同时导出命名 let 绑定(供 named import 使用)和 default 对象(供 import state 使用)\n// default 对象通过 getter/setter 代理到同名 let 变量,确保两种导入方式共享同一份数据\n\nexport let generatingSessionIds = new Set(); // Set of sessionIds currently generating\n// 后台任务已完成、但用户尚未切回查看的会话 ID 集合\n// 用于在会话标签上显示\"完成待查看\"提示,用户切回该会话后清除\nexport let completedSessionIds = new Set();\nexport let messageHistory = [];\nexport let currentModel = 'deepseek-v4-pro';\nexport let activeSessionId = null; // 当前活跃会话 ID\nexport let sessions = []; // 所有会话列表缓存\nexport let useTools = true;\nexport let isolateChat = true;\n// 当前会话的上下文压缩记录(会话级,随会话持久化;null 表示未压缩)\nexport let activeCompaction = null;\nexport let enableSelectionQuery = false;\nexport let currentTabId = null;\nexport let selectedContextText = '';\nexport let quotedContextText = '';\nexport let customPrompts = [];\nexport let selectedPromptIndex = -1;\nexport let selectedSkillIndex = -1; // 技能选择器当前选中索引\nexport let selectedSkills = []; // 当前选中的技能列表(支持多选)[{ name, description, type }]\nexport let selectedMcpServices = []; // 当前选中的 MCP 服务列表(支持多选)[{ serverId, serverName, toolCount }]\nexport let selectedMcpServiceIndex = -1; // MCP 服务选择器当前选中索引\nexport let selectedPage = null; // 当前选中的网页 { id, title, url, favIconUrl }\nexport let selectedPageIndex = -1; // 页面选择器当前选中索引\nexport let knowledgeRefs = []; // 已引用的知识库列表 [{ id, name }]\nexport let selectedKnowledgeAtIndex = -1; // @ 知识库选择器当前选中索引\nexport let activeDropdownTab = 'prompts'; // 下拉框当前激活的 Tab\nexport let lastActiveDropdownTab = 'prompts'; // 上次激活的 Tab(用于记忆)\nexport let showMergedList = false; // 是否显示合并列表(搜索模式)\nexport let selectedAgentAtIndex = -1; // @ Agent 选择器当前选中索引\nexport let selectedProxyAtIndex = -1; // @ 代理选择器当前选中索引\nexport let selectedFileAtIndex = -1; // $ 工作目录文件选择器当前选中索引\nexport let draggedItemIndex = null;\nexport let systemPrompt = '';\nexport let inputHistory = [];\nexport let inputHistoryIndex = -1;\n\n// 自定义 Agent 相关\nexport let activeAgentId = null; // 当前选中的 Agent ID\nexport let activeAgentToolIds = null; // 当前 Agent 限定的工具列表(null=继承全局)\nexport let activeAgentMcpExcludedServerIds = null; // 当前 Agent 排除的 MCP 服务 ID 列表(服务级;null=不限)\nexport let activeAgentSkillIds = null; // 当前 Agent 绑定的技能名称列表(null=全部启用)\nexport let customAgents = []; // 用户自定义 Agent 列表缓存\n\n// Agent 平台信息(从 Agent 获取,用于系统提示词注入)\nexport let agentPlatform = {\n platformName: 'Unknown',\n platform: 'unknown',\n arch: 'unknown',\n shell: '/bin/sh',\n homeDir: '/home/user',\n workdir: '',\n connected: false\n};\n\n// 配置常量 - 从 storage 获取,使用默认值作为后备\nexport let chatConfig = {\n maxMemoryMessages: 20,\n enableExecutionLog: false,\n showMessageTimestamp: true\n};\n\n// 温度设置\nexport let temperature = 0.2;\nexport let topP = 1.0;\nexport let selectedTempIndex = 0;\n\n// 工具弹窗搜索和分类状态\nexport let currentCategory = 'all';\nexport let currentSearch = '';\n\n// 当前启用的工具列表(不含 MCP 工具,MCP 按服务级开关管理)\nexport let enabledTools = [];\n\n// 当前智能体上下文中被用户关闭的 MCP 服务 ID 列表(deny-list:不在列表中 = 开放)\nexport let mcpClosedServers = [];\n\n// 分类折叠状态\nexport const collapsedCategories = {};\n\n// 当前执行状态(按 sessionId 隔离,key 为 sessionId)\nexport let sessionExecutionStatus = new Map();\nexport let executionLogListener = null;\n\n// 当前 API 调用的取消函数,按 sessionId 隔离,防止多会话并行时互相覆盖\nexport let pendingCancelApiMap = new Map();\n\n// 记录有 pending callApi 的会话 ID 集合(支持多会话同时有后台任务)\nexport let pendingCallApiSessionIds = new Set();\n\n// 切换回原会话后,重新创建的加载指示器 ID 集合(按 sessionId 索引)\nexport let substituteLoadingIds = new Map();\n\n// 澄清对话框相关状态\nexport let currentClarifyToolCallId = null;\nexport let currentClarifySessionId = null; // 当前澄清所属的会话 ID\nexport let currentConfirmToolCallId = null; // 当前确认对话框所属的 toolCallId\nexport let currentConfirmSessionId = null; // 当前确认对话框所属的会话 ID\nexport let clarifyTimerInterval = null;\nexport let clarifyTimeoutValue = 300000; // 默认 5 分钟\n\n// 消息目录状态\nexport let messageTocContainer = null;\nexport let isMouseOverToc = false;\nexport let tocHideTimer = null;\n\n// 消息收藏\nexport let bookmarks = [];\n\n// 划词问答弹出菜单状态\nexport let lastSelectedText = '';\nexport let currentSelectionRange = null;\nexport let lastMouseX = 0;\nexport let lastMouseY = 0;\n\n// 待删除的提示词索引\nexport let pendingDeleteIndex = -1;\n\n// 图片识别\nexport let enableImageInput = false;\nexport let imageModelName = ''; // 图片识别模型,为空则使用 currentModel\nexport let imageApiBase = ''; // 图片识别独立 API Base,为空则使用主配置\nexport let imageApiKey = ''; // 图片识别独立 API Token,为空则使用主配置\nexport let attachedImages = []; // [{ originalUrl: 'data:image/jpeg;base64,...', compressedUrl: 'data:image/jpeg;base64,...' }]\n\n// 文件上传问答\nexport let enableFileInput = true; // 文件上传开关\nexport let attachedFiles = []; // [{ name, size, type, dataUrl, text, status: 'pending'|'extracting'|'done'|'error' }]\n\n// 代理版本号缓存 (agentId -> version string)\nexport let agentVersions = new Map();\n\n// 代理工作目录缓存 (agentId -> workdir string)\nexport let agentWorkdirs = new Map();\n\n// 代理用户主目录缓存 (agentId -> homeDir string),用于将工作目录前缀替换为 ~\nexport let agentHomeDirs = new Map();\n\n// 代理系统信息缓存 (agentId -> \"macOS (x64)\" 格式字符串)\nexport let agentSystemInfos = new Map();\n\n// 标志位\nexport let isScrolling = false;\nexport let isComposing = false;\n\n// 自定义模型上下文窗口映射(modelName → contextWindow)\nexport let customModelMap = new Map();\n\n// ============================================================\n// default 导出:通过 getter/setter 代理到同名 let 变量\n// 使用方式: import state from './state.js'; state.xxx = value;\n// ============================================================\nexport default {\n get isGenerating() { return generatingSessionIds.has(activeSessionId); },\n set isGenerating(v) { \n if (v) generatingSessionIds.add(activeSessionId); \n else generatingSessionIds.delete(activeSessionId);\n document.dispatchEvent(new CustomEvent('generating-state-changed'));\n },\n get generatingSessionIds() { return generatingSessionIds; },\n get completedSessionIds() { return completedSessionIds; },\n set completedSessionIds(v) { completedSessionIds = v; },\n get messageHistory() { return messageHistory; },\n set messageHistory(v) { messageHistory = v; },\n get currentModel() { return currentModel; },\n set currentModel(v) { currentModel = v; },\n get activeSessionId() { return activeSessionId; },\n set activeSessionId(v) { activeSessionId = v; },\n get sessions() { return sessions; },\n set sessions(v) { sessions = v; },\n get useTools() { return useTools; },\n set useTools(v) { useTools = v; },\n get isolateChat() { return isolateChat; },\n set isolateChat(v) { isolateChat = v; },\n get activeCompaction() { return activeCompaction; },\n set activeCompaction(v) { activeCompaction = v; },\n get enableSelectionQuery() { return enableSelectionQuery; },\n set enableSelectionQuery(v) { enableSelectionQuery = v; },\n get currentTabId() { return currentTabId; },\n set currentTabId(v) { currentTabId = v; },\n get selectedContextText() { return selectedContextText; },\n set selectedContextText(v) { selectedContextText = v; },\n get quotedContextText() { return quotedContextText; },\n set quotedContextText(v) { quotedContextText = v; },\n get customPrompts() { return customPrompts; },\n set customPrompts(v) { customPrompts = v; },\n get selectedPromptIndex() { return selectedPromptIndex; },\n set selectedPromptIndex(v) { selectedPromptIndex = v; },\n get selectedSkillIndex() { return selectedSkillIndex; },\n set selectedSkillIndex(v) { selectedSkillIndex = v; },\n get selectedSkills() { return selectedSkills; },\n set selectedSkills(v) { selectedSkills = v; },\n get selectedMcpServices() { return selectedMcpServices; },\n set selectedMcpServices(v) { selectedMcpServices = v; },\n get selectedMcpServiceIndex() { return selectedMcpServiceIndex; },\n set selectedMcpServiceIndex(v) { selectedMcpServiceIndex = v; },\n get selectedPage() { return selectedPage; },\n set selectedPage(v) { selectedPage = v; },\n get selectedPageIndex() { return selectedPageIndex; },\n set selectedPageIndex(v) { selectedPageIndex = v; },\n get knowledgeRefs() { return knowledgeRefs; },\n set knowledgeRefs(v) { knowledgeRefs = v; },\n get selectedKnowledgeAtIndex() { return selectedKnowledgeAtIndex; },\n set selectedKnowledgeAtIndex(v) { selectedKnowledgeAtIndex = v; },\n get activeDropdownTab() { return activeDropdownTab; },\n set activeDropdownTab(v) { activeDropdownTab = v; },\n get lastActiveDropdownTab() { return lastActiveDropdownTab; },\n set lastActiveDropdownTab(v) { lastActiveDropdownTab = v; },\n get showMergedList() { return showMergedList; },\n set showMergedList(v) { showMergedList = v; },\n get selectedAgentAtIndex() { return selectedAgentAtIndex; },\n set selectedAgentAtIndex(v) { selectedAgentAtIndex = v; },\n get selectedProxyAtIndex() { return selectedProxyAtIndex; },\n set selectedProxyAtIndex(v) { selectedProxyAtIndex = v; },\n get selectedFileAtIndex() { return selectedFileAtIndex; },\n set selectedFileAtIndex(v) { selectedFileAtIndex = v; },\n get draggedItemIndex() { return draggedItemIndex; },\n set draggedItemIndex(v) { draggedItemIndex = v; },\n get systemPrompt() { return systemPrompt; },\n set systemPrompt(v) { systemPrompt = v; },\n get activeAgentId() { return activeAgentId; },\n set activeAgentId(v) { activeAgentId = v; },\n get activeAgentToolIds() { return activeAgentToolIds; },\n set activeAgentToolIds(v) { activeAgentToolIds = v; },\n get activeAgentMcpExcludedServerIds() { return activeAgentMcpExcludedServerIds; },\n set activeAgentMcpExcludedServerIds(v) { activeAgentMcpExcludedServerIds = v; },\n get activeAgentSkillIds() { return activeAgentSkillIds; },\n set activeAgentSkillIds(v) { activeAgentSkillIds = v; },\n get customAgents() { return customAgents; },\n set customAgents(v) { customAgents = v; },\n get agentPlatform() { return agentPlatform; },\n set agentPlatform(v) { Object.assign(agentPlatform, v); },\n get inputHistory() { return inputHistory; },\n set inputHistory(v) { inputHistory = v; },\n get inputHistoryIndex() { return inputHistoryIndex; },\n set inputHistoryIndex(v) { inputHistoryIndex = v; },\n get chatConfig() { return chatConfig; },\n set chatConfig(v) { chatConfig = v; },\n get temperature() { return temperature; },\n set temperature(v) { temperature = v; },\n get topP() { return topP; },\n set topP(v) { topP = v; },\n get selectedTempIndex() { return selectedTempIndex; },\n set selectedTempIndex(v) { selectedTempIndex = v; },\n get currentCategory() { return currentCategory; },\n set currentCategory(v) { currentCategory = v; },\n get currentSearch() { return currentSearch; },\n set currentSearch(v) { currentSearch = v; },\n get enabledTools() { return enabledTools; },\n set enabledTools(v) { enabledTools = v; },\n get mcpClosedServers() { return mcpClosedServers; },\n set mcpClosedServers(v) { mcpClosedServers = v; },\n get collapsedCategories() { return collapsedCategories; },\n // collapsedCategories is const, no setter needed\n get sessionExecutionStatus() { return sessionExecutionStatus; },\n set sessionExecutionStatus(v) { sessionExecutionStatus = v; },\n // 兼容旧代码:currentExecutionStatus 通过 getter/setter 操作当前活跃会话\n get currentExecutionStatus() { return sessionExecutionStatus.get(activeSessionId) || null; },\n set currentExecutionStatus(v) { \n if (v === null) {\n sessionExecutionStatus.delete(activeSessionId);\n } else {\n sessionExecutionStatus.set(activeSessionId, v);\n }\n },\n get executionLogListener() { return executionLogListener; },\n set executionLogListener(v) { executionLogListener = v; },\n get pendingCancelApi() { return pendingCancelApiMap.get(activeSessionId) || null; },\n set pendingCancelApi(v) {\n if (v === null) {\n pendingCancelApiMap.delete(activeSessionId);\n } else {\n pendingCancelApiMap.set(activeSessionId, v);\n }\n },\n get pendingCancelApiMap() { return pendingCancelApiMap; },\n get pendingCallApiSessionIds() { return pendingCallApiSessionIds; },\n set pendingCallApiSessionIds(v) { pendingCallApiSessionIds = v; },\n get substituteLoadingIds() { return substituteLoadingIds; },\n set substituteLoadingIds(v) { substituteLoadingIds = v; },\n get currentClarifyToolCallId() { return currentClarifyToolCallId; },\n set currentClarifyToolCallId(v) { currentClarifyToolCallId = v; },\n get currentClarifySessionId() { return currentClarifySessionId; },\n set currentClarifySessionId(v) { currentClarifySessionId = v; },\n get currentConfirmToolCallId() { return currentConfirmToolCallId; },\n set currentConfirmToolCallId(v) { currentConfirmToolCallId = v; },\n get currentConfirmSessionId() { return currentConfirmSessionId; },\n set currentConfirmSessionId(v) { currentConfirmSessionId = v; },\n get clarifyTimerInterval() { return clarifyTimerInterval; },\n set clarifyTimerInterval(v) { clarifyTimerInterval = v; },\n get clarifyTimeoutValue() { return clarifyTimeoutValue; },\n set clarifyTimeoutValue(v) { clarifyTimeoutValue = v; },\n get messageTocContainer() { return messageTocContainer; },\n set messageTocContainer(v) { messageTocContainer = v; },\n get isMouseOverToc() { return isMouseOverToc; },\n set isMouseOverToc(v) { isMouseOverToc = v; },\n get tocHideTimer() { return tocHideTimer; },\n set tocHideTimer(v) { tocHideTimer = v; },\n get bookmarks() { return bookmarks; },\n set bookmarks(v) { bookmarks = v; },\n get lastSelectedText() { return lastSelectedText; },\n set lastSelectedText(v) { lastSelectedText = v; },\n get currentSelectionRange() { return currentSelectionRange; },\n set currentSelectionRange(v) { currentSelectionRange = v; },\n get lastMouseX() { return lastMouseX; },\n set lastMouseX(v) { lastMouseX = v; },\n get lastMouseY() { return lastMouseY; },\n set lastMouseY(v) { lastMouseY = v; },\n get pendingDeleteIndex() { return pendingDeleteIndex; },\n set pendingDeleteIndex(v) { pendingDeleteIndex = v; },\n get enableImageInput() { return enableImageInput; },\n set enableImageInput(v) { enableImageInput = v; },\n get imageModelName() { return imageModelName; },\n set imageModelName(v) { imageModelName = v; },\n get imageApiBase() { return imageApiBase; },\n set imageApiBase(v) { imageApiBase = v; },\n get imageApiKey() { return imageApiKey; },\n set imageApiKey(v) { imageApiKey = v; },\n get attachedImages() { return attachedImages; },\n set attachedImages(v) { attachedImages = v; },\n get enableFileInput() { return enableFileInput; },\n set enableFileInput(v) { enableFileInput = v; },\n get attachedFiles() { return attachedFiles; },\n set attachedFiles(v) { attachedFiles = v; },\n get agentVersions() { return agentVersions; },\n set agentVersions(v) { agentVersions = v; },\n get agentWorkdirs() { return agentWorkdirs; },\n set agentWorkdirs(v) { agentWorkdirs = v; },\n get agentHomeDirs() { return agentHomeDirs; },\n set agentHomeDirs(v) { agentHomeDirs = v; },\n get agentSystemInfos() { return agentSystemInfos; },\n set agentSystemInfos(v) { agentSystemInfos = v; },\n get isScrolling() { return isScrolling; },\n set isScrolling(v) { isScrolling = v; },\n get isComposing() { return isComposing; },\n set isComposing(v) { isComposing = v; },\n get customModelMap() { return customModelMap; },\n set customModelMap(v) { customModelMap = v; },\n};\n","// storage/session-store.js - 会话存储适配器\n// 对接 IndexedDB,提供与旧 chrome.storage 兼容的 API\n// 同时保留 chrome.storage.local 兜底(双写 + 读降级)\n\nimport * as idb from './db.js';\nimport state from '../side_panel/state.js';\nimport logger from '../shared/logger.js';\nimport { t, registerTranslations } from '../shared/i18n.js';\n\nregisterTranslations('zh', {\n session: {\n newSession: '新会话',\n importedSession: '导入的会话',\n unnamedSession: '未命名会话',\n restoredSession: '恢复的会话',\n forkSuffix: '分叉',\n sourceNotFound: '源会话不存在',\n scheduledSession: '定时任务',\n },\n});\nregisterTranslations('en', {\n session: {\n newSession: 'New session',\n importedSession: 'Imported session',\n unnamedSession: 'Unnamed session',\n restoredSession: 'Restored session',\n forkSuffix: 'Fork',\n sourceNotFound: 'Source session not found',\n scheduledSession: 'Scheduled task',\n },\n});\n\n// 会话删除时需要同步清理对应的 ReAct checkpoint\nasync function cleanupCheckpointForSession(sessionId) {\n try {\n await idb.deleteReactCheckpoint(sessionId);\n } catch (e) {\n logger.warn('[session-store] cleanup ReAct checkpoint failed:', e);\n }\n}\n\nlet migrated = false;\n\n/**\n * 初始化:尝试从 chrome.storage 迁移数据\n */\nexport async function init() {\n if (migrated) return;\n await idb.ensureMigration();\n migrated = true;\n\n // 清理孤儿 scrollPosition key(会话已被删除但 key 残留)\n cleanupOrphanScrollPositions();\n}\n\n/**\n * 清理 chrome.storage.local 中无对应会话的 scrollPosition_* key\n */\nasync function cleanupOrphanScrollPositions() {\n try {\n const sessions = await idb.getAllSessions();\n const validIds = new Set(sessions.map(s => s.id));\n // 'default' 是 fallback key,始终保留\n validIds.add('default');\n\n const allItems = await chrome.storage.local.get(null);\n const orphanKeys = Object.keys(allItems).filter(\n k => k.startsWith('scrollPosition_') && !validIds.has(k.slice('scrollPosition_'.length))\n );\n\n if (orphanKeys.length > 0) {\n await chrome.storage.local.remove(orphanKeys);\n logger.debug(`[session-store] cleanup ${orphanKeys.length} orphan scrollPosition key:`, orphanKeys);\n }\n } catch (e) {\n // 非关键路径,静默失败\n logger.warn('[session-store] cleanup orphaned scrollPosition failed:', e);\n }\n}\n\n/**\n * 加载所有会话(兼容旧 API 返回格式)\n * @returns {Promise<{activeSessionId: string|null, list: Array}>}\n */\nexport async function loadSessions() {\n await init();\n const [list, activeId] = await Promise.all([\n idb.getAllSessions(),\n idb.getActiveSessionId(),\n ]);\n // 按 order 排序,没有 order 的按 createdAt 兜底\n list.sort((a, b) => {\n if (a.order !== undefined && b.order !== undefined) return a.order - b.order;\n if (a.order !== undefined) return -1;\n if (b.order !== undefined) return 1;\n return new Date(a.createdAt) - new Date(b.createdAt);\n });\n return { activeSessionId: activeId, list };\n}\n\n/**\n * 保存所有活跃会话到存储\n * @param {{activeSessionId: string|null, list: Array}} sessionsData\n */\nexport async function saveSessions(sessionsData) {\n await init();\n const { list, activeSessionId } = sessionsData;\n await Promise.all([\n idb.putSessions(list),\n idb.setActiveSessionId(activeSessionId),\n ]);\n return true;\n}\n\n/**\n * 从消息 content 中提取纯文本(content 可能是数组格式含图片)\n */\nfunction getTextContent(content) {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content.filter(c => c.type === 'text').map(c => c.text).join('');\n }\n return '';\n}\n\n/**\n * 保存当前会话状态\n */\nexport async function saveCurrentSession() {\n if (!state.activeSessionId) return false;\n\n const currentSession = await idb.getSession(state.activeSessionId);\n if (!currentSession) {\n logger.warn('[SessionStore] saveCurrentSession cannot find currentsession:', state.activeSessionId);\n return false;\n }\n\n // 更新当前会话数据\n currentSession.model = state.currentModel;\n currentSession.useTools = state.useTools;\n currentSession.enabledTools = [...state.enabledTools];\n currentSession.agentId = state.activeAgentId || null;\n currentSession.temperature = state.temperature;\n currentSession.topP = state.topP;\n // 上下文压缩记录(会话级;undefined 时 JSON 序列化自动省略字段)\n currentSession.contextCompaction = state.activeCompaction || undefined;\n\n currentSession.messageHistory = state.messageHistory.map((msg) => ({\n role: msg.role,\n content: msg.content || '',\n executionLog: msg.executionLog || [],\n reflectionScore: msg.reflectionScore,\n wasRevised: msg.wasRevised || false,\n htmlContent: msg.htmlContent || undefined,\n messageId: msg.messageId || undefined,\n timestamp: msg.timestamp || undefined,\n contextBubbles: msg.contextBubbles || undefined,\n resumable: msg.resumable || false,\n }));\n\n currentSession.updatedAt = new Date().toISOString();\n\n // 更新标题(取第一条用户消息的前 50 个字符)\n // 分叉/复制的会话保留其自定义标题(含\"- 分叉\"/\"- 副本\"后缀),不自动覆盖\n if (!currentSession.forkMetadata) {\n const firstUserMsg = state.messageHistory.find((m) => m.role === 'user');\n if (firstUserMsg) {\n currentSession.title = getTextContent(firstUserMsg.content).substring(0, 50).replace(/\\n/g, ' ');\n }\n }\n\n const saved = await idb.putSession(currentSession);\n if (!saved) {\n logger.error('[SessionStore] putSession failed, sessionId:', state.activeSessionId);\n return false;\n }\n return true;\n}\n\n/**\n * 创建一个新会话\n * @returns {Promise<Object>} 新会话对象\n */\nexport async function createSession() {\n await init();\n const sessionId = generateSessionId();\n const newSession = {\n id: sessionId,\n title: t('session.newSession'),\n model: state.currentModel,\n useTools: state.useTools,\n enabledTools: [...state.enabledTools],\n agentId: state.activeAgentId || null,\n temperature: state.temperature,\n topP: state.topP,\n messageHistory: [],\n scrollPosition: 0,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n order: Date.now(),\n isGenerating: false,\n lastExecutionLog: [],\n };\n\n await idb.putSession(newSession);\n await idb.setActiveSessionId(sessionId);\n return newSession;\n}\n\n/**\n * 从导出的会话数据导入为新的活跃会话\n * @param {Array} sessionsData - 导出的会话数据数组(不含 id/order,导入时重新生成)\n * @returns {Promise<Array>} 新创建的会话数组\n */\nexport async function importSessions(sessionsData) {\n await init();\n const createdSessions = [];\n\n for (const sessionData of sessionsData) {\n const sessionId = generateSessionId();\n const newSession = {\n id: sessionId,\n title: sessionData.title || t('session.importedSession'),\n model: sessionData.model || state.currentModel,\n useTools: sessionData.useTools !== undefined ? sessionData.useTools : true,\n enabledTools: sessionData.enabledTools || [...state.enabledTools],\n temperature: sessionData.temperature !== undefined ? sessionData.temperature : state.temperature,\n topP: sessionData.topP !== undefined ? sessionData.topP : state.topP,\n agentId: sessionData.agentId || null,\n messageHistory: (sessionData.messageHistory || []).map((msg) => ({\n role: msg.role,\n content: msg.content || '',\n executionLog: msg.executionLog || [],\n reflectionScore: msg.reflectionScore,\n wasRevised: msg.wasRevised || false,\n htmlContent: msg.htmlContent || undefined,\n messageId: msg.messageId || undefined,\n timestamp: msg.timestamp || undefined,\n })),\n scrollPosition: 0,\n createdAt: sessionData.createdAt || new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n order: Date.now() + createdSessions.length,\n isGenerating: false,\n lastExecutionLog: [],\n };\n\n await idb.putSession(newSession);\n createdSessions.push(newSession);\n }\n\n // 如果当前没有活跃会话,自动切换到第一个导入的会话\n const activeId = await idb.getActiveSessionId();\n if (!activeId && createdSessions.length > 0) {\n await idb.setActiveSessionId(createdSessions[0].id);\n }\n\n return createdSessions;\n}\n\n/**\n * 将一条消息追加到指定会话的消息历史中\n * 用于切换会话后,后台任务仍在执行时保存任务结果\n * @param {string} sessionId 目标会话 ID\n * @param {Object} message 消息对象 { role, content, executionLog }\n */\nexport async function appendMessageToSession(sessionId, message) {\n const session = await idb.getSession(sessionId);\n if (!session) return false;\n\n session.messageHistory = session.messageHistory || [];\n session.messageHistory.push({\n role: message.role,\n content: message.content || '',\n executionLog: message.executionLog || [],\n reflectionScore: message.reflectionScore,\n wasRevised: message.wasRevised || false,\n htmlContent: message.htmlContent || undefined,\n messageId: message.messageId || undefined,\n timestamp: message.timestamp || undefined,\n contextBubbles: message.contextBubbles || undefined,\n });\n\n session.updatedAt = new Date().toISOString();\n session.isGenerating = false;\n\n await idb.putSession(session);\n return true;\n}\n\n/**\n * 生成唯一会话 ID\n */\nfunction generateSessionId() {\n return 'sess_' + Date.now().toString(36) + '_' + Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * 切换到指定会话\n * @returns {Promise<Object|false>}\n */\nexport async function switchToSession(sessionId) {\n if (sessionId === state.activeSessionId) return;\n\n // 并发化:保存当前会话与读取目标会话互相独立,同时发起。\n // 红线保留:saveCurrentSession 仍是第一个动作(读旧 state 保存),\n // 且所有 state 赋值都在 Promise.all 完成后执行;默认 Agent 的全局模型\n // 配置读取也无副作用,一并提前发起(结果仍在后文分支中应用)。\n const savePromise = saveCurrentSession();\n const globalConfigPromise = chrome.storage.local.get(['modelName', 'temperature', 'topP']).catch(() => ({}));\n\n const [targetSession] = await Promise.all([idb.getSession(sessionId), savePromise]);\n if (!targetSession) {\n logger.error('[SessionStore] find not to session:', sessionId);\n return false;\n }\n\n await idb.setActiveSessionId(sessionId);\n\n state.activeSessionId = sessionId;\n state.messageHistory = targetSession.messageHistory || [];\n // 恢复会话级上下文压缩记录(无则置 null)\n state.activeCompaction = targetSession.contextCompaction || null;\n // 会话切换时从会话恢复 model/temperature\n // 如果会话绑定了 Agent,优先使用 Agent 配置;否则使用会话存储的值\n state.currentModel = targetSession.model || state.currentModel;\n state.temperature = targetSession.temperature !== undefined ? targetSession.temperature : state.temperature;\n state.topP = targetSession.topP !== undefined ? targetSession.topP : state.topP;\n state.useTools = targetSession.useTools !== undefined ? targetSession.useTools : state.useTools;\n state.activeAgentId = targetSession.agentId || null;\n // 持久化当前智能体 ID,避免刷新后丢失\n if (targetSession.agentId) {\n chrome.storage.local.set({ activeAssistantId: targetSession.agentId });\n } else {\n chrome.storage.local.remove('activeAssistantId');\n }\n\n // 如果会话绑定了自定义 Agent,尝试从 Agent 配置中加载模型/温度\n // 注意:自定义 Agent 的参数只更新 state,不写入 chrome.storage.local 全局键,\n // 避免覆盖默认 Agent 的全局模型/温度值\n if (targetSession.agentId && targetSession.agentId !== 'default') {\n try {\n const { getAgent } = await import('../side_panel/agent-store.js');\n const agent = await getAgent(targetSession.agentId);\n if (agent) {\n if (agent.model) {\n state.currentModel = agent.model;\n }\n if (agent.temperature !== null && agent.temperature !== undefined) {\n state.temperature = agent.temperature;\n state.topP = agent.topP !== null && agent.topP !== undefined ? agent.topP : 1.0;\n }\n // 触发 UI 更新\n document.dispatchEvent(new CustomEvent('agent-model-changed'));\n }\n } catch { /* Agent 加载失败,使用会话存储值 */ }\n } else {\n // 默认 Agent:从 chrome.storage.local 读取全局模型/温度(所有默认 Agent 会话共享)\n // 注:该读取已随切换流程并行发起(见函数开头),此处仅应用结果\n const global = await globalConfigPromise;\n if (global.modelName) state.currentModel = global.modelName;\n if (global.temperature !== undefined) state.temperature = global.temperature;\n if (global.topP !== undefined) state.topP = global.topP;\n // 触发 UI 更新,确保弹窗 slider/输入框与图标一致\n document.dispatchEvent(new CustomEvent('agent-model-changed'));\n }\n // 使用按会话隔离的 generatingSessionIds Set 恢复生成状态\n // 仅当 pendingCallApiSessionIds 中也存在该 session 时才恢复,防止 SW 重启后\n // DB 中残留的 isGenerating=true 导致虚假的\"生成中\"状态\n if (targetSession.isGenerating && state.pendingCallApiSessionIds.has(sessionId)) {\n state.generatingSessionIds.add(sessionId);\n } else if (targetSession.isGenerating) {\n // DB 中有生成标记但实际无后台任务,异步清理过期状态(不阻塞切换)\n logger.debug('[SessionStore] detected expiredperiod isGenerating mark,async cleanup:', sessionId);\n (async () => {\n try {\n const session = await idb.getSession(sessionId);\n if (session) {\n session.isGenerating = false;\n await idb.putSession(session);\n }\n } catch (e) {\n logger.warn('[SessionStore] cleanup expired isGenerating mark failed:', e);\n }\n })();\n }\n\n return targetSession;\n}\n\n/**\n * 删除指定会话\n */\nexport async function deleteStoreSession(sessionId) {\n const allSessions = await idb.getAllSessions();\n const activeId = await idb.getActiveSessionId();\n\n await idb.deleteSession(sessionId);\n\n // 清理该会话的 scrollPosition 存储,防止泄露\n chrome.storage.local.remove('scrollPosition_' + sessionId);\n\n // 同步清理该会话的 ReAct checkpoint\n await cleanupCheckpointForSession(sessionId);\n\n // 清理该会话的收藏\n try {\n const allBookmarks = await idb.getAllBookmarks();\n const toDelete = allBookmarks.filter(b => b.sessionId === sessionId);\n for (const bm of toDelete) {\n await idb.deleteBookmark(bm.id);\n }\n if (toDelete.length > 0) {\n logger.info(`[SessionStore] cleaned ${toDelete.length} bookmark (session ${sessionId} deleted)`);\n }\n } catch (e) {\n logger.warn('[SessionStore] cleanup bookmarks failed:', e);\n }\n\n // 如果删除的是当前活跃会话,就近激活:优先右侧紧邻,无右侧则左侧紧邻\n if (activeId === sessionId) {\n // 按显示顺序排序(与 loadSessions 保持一致)\n const sorted = [...allSessions].sort((a, b) => {\n if (a.order !== undefined && b.order !== undefined) return a.order - b.order;\n if (a.order !== undefined) return -1;\n if (b.order !== undefined) return 1;\n return new Date(a.createdAt) - new Date(b.createdAt);\n });\n const deletedIndex = sorted.findIndex(s => s.id === sessionId);\n const remaining = sorted.filter((s) => s.id !== sessionId);\n\n if (remaining.length > 0) {\n // 就近激活:取同位置(右侧邻居),若已超出则取最后一个(左侧邻居)\n const fallbackIndex = Math.min(deletedIndex, remaining.length - 1);\n await idb.setActiveSessionId(remaining[fallbackIndex].id);\n } else {\n await idb.setActiveSessionId(null);\n }\n }\n\n return true;\n}\n\n/**\n * 重命名会话\n */\nexport async function renameSession(sessionId, newTitle) {\n const session = await idb.getSession(sessionId);\n if (!session) return false;\n session.title = newTitle;\n session.updatedAt = new Date().toISOString();\n await idb.putSession(session);\n return true;\n}\n\n/**\n * 重新排序会话\n * @param {string[]} orderedIds - 按新顺序排列的会话 ID 列表\n */\nexport async function reorderSessions(orderedIds) {\n const allSessions = await idb.getAllSessions();\n const sessionMap = new Map(allSessions.map(s => [s.id, s]));\n\n const updates = [];\n orderedIds.forEach((id, index) => {\n const session = sessionMap.get(id);\n if (session) {\n session.order = index;\n updates.push(idb.putSession(session));\n }\n });\n\n await Promise.all(updates);\n return true;\n}\n\n/**\n * 归档当前会话\n */\nexport async function archiveCurrentSession() {\n if (!state.messageHistory || state.messageHistory.length === 0) return;\n\n const currentSession = await idb.getSession(state.activeSessionId);\n if (!currentSession) return;\n\n const firstUserMsg = state.messageHistory.find((m) => m.role === 'user');\n const title = firstUserMsg\n ? getTextContent(firstUserMsg.content).substring(0, 50).replace(/\\n/g, ' ')\n : currentSession.title || t('session.unnamedSession');\n\n const archivedId = Date.now().toString(36) + Math.random().toString(36).substring(2, 8);\n\n const sanitizedMessages = state.messageHistory.map((msg) => ({\n role: msg.role,\n content: msg.content || '',\n executionLog: msg.executionLog || [],\n reflectionScore: msg.reflectionScore,\n wasRevised: msg.wasRevised || false,\n htmlContent: msg.htmlContent || undefined,\n }));\n\n // 保存到归档\n const archiveSessions = await idb.getAllArchivedSessions();\n archiveSessions.push({\n id: archivedId,\n title,\n createdAt: new Date().toISOString(),\n messages: sanitizedMessages,\n });\n\n // 最多保留 20 个归档\n if (archiveSessions.length > 20) {\n archiveSessions.splice(0, archiveSessions.length - 20);\n }\n\n await idb.replaceArchivedSessions(archiveSessions);\n\n // 从活跃会话列表中移除,保留一个空的新会话\n const newEmptySession = await createSession();\n await deleteStoreSession(currentSession.id);\n await switchToSession(newEmptySession.id);\n\n return true;\n}\n\n/**\n * 恢复归档会话为活跃会话\n */\nexport async function restoreArchivedSession(archivedId) {\n const archiveSessions = await idb.getAllArchivedSessions();\n const archived = archiveSessions.find((s) => s.id === archivedId);\n\n if (!archived) return false;\n\n // 创建新活跃会话\n const sessionId = generateSessionId();\n const newSession = {\n id: sessionId,\n title: archived.title || t('session.restoredSession'),\n model: state.currentModel,\n useTools: state.useTools,\n enabledTools: [...state.enabledTools],\n temperature: state.temperature,\n topP: state.topP,\n messageHistory: archived.messages || [],\n scrollPosition: 0,\n createdAt: archived.createdAt || new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n isGenerating: false,\n lastExecutionLog: [],\n };\n\n await idb.putSession(newSession);\n await idb.setActiveSessionId(sessionId);\n\n // 从归档中移除\n const archiveIdx = archiveSessions.findIndex((s) => s.id === archivedId);\n if (archiveIdx !== -1) {\n archiveSessions.splice(archiveIdx, 1);\n }\n await idb.replaceArchivedSessions(archiveSessions);\n\n return newSession;\n}\n\n/**\n * 复制会话(完整快照或消息级截断,作为对话分支使用)\n * 完整继承源会话的消息历史与配置(模型/工具/Agent/温度等)\n * @param {string} sourceSessionId - 源会话 ID\n * @param {string|null} [upToMessageId=null] - 消息级分叉:截断到此 messageId(含)为止\n * 传入时仅复制到该消息(含),不传则完整复制全部历史\n * @returns {Promise<Object>} 新创建的会话\n */\nexport async function duplicateSession(sourceSessionId, upToMessageId = null) {\n await init();\n\n const source = await idb.getSession(sourceSessionId);\n if (!source) throw new Error(t('session.sourceNotFound'));\n\n const newSessionId = generateSessionId();\n const now = new Date().toISOString();\n\n // 消息级分叉:截断到指定 messageId(含);找不到则回退完整复制\n let srcMessages = source.messageHistory || [];\n let isMessageFork = false;\n if (upToMessageId) {\n const idx = srcMessages.findIndex(m => m.messageId === upToMessageId);\n if (idx >= 0) {\n srcMessages = srcMessages.slice(0, idx + 1); // 含该消息\n isMessageFork = true;\n }\n }\n\n // 完整复制消息历史,深拷贝避免引用共享;重新生成 messageId 避免冲突\n const clonedMessages = srcMessages.map((msg) => ({\n ...msg,\n executionLog: Array.isArray(msg.executionLog) ? [...msg.executionLog] : [],\n contextBubbles: Array.isArray(msg.contextBubbles) ? [...msg.contextBubbles] : undefined,\n // 重新生成 messageId(遵循项目约定:所有消息必须有唯一 messageId)\n messageId: 'msg_' + Date.now().toString(36) + '_' + Math.random().toString(36).substring(2, 8),\n }));\n\n const newSession = {\n ...source,\n id: newSessionId,\n title: `${source.title || t('session.newSession')} - ${t('session.forkSuffix')}`,\n messageHistory: clonedMessages,\n // 重置运行时状态\n isGenerating: false,\n lastExecutionLog: [],\n scrollPosition: 0,\n createdAt: now,\n updatedAt: now,\n order: Date.now(),\n // 分支元数据:记录来源与分叉点\n forkMetadata: {\n sourceSessionId,\n forkedAt: now,\n ...(isMessageFork ? { upToMessageId } : {}),\n },\n };\n\n await idb.putSession(newSession);\n await idb.setActiveSessionId(newSessionId);\n\n return newSession;\n}\n\n/**\n * 为定时任务创建专属宿主会话(当任务绑定的会话被删除时自愈使用)\n * 与普通会话一致,仅额外标记 scheduledTaskId,便于识别 与 后续清理\n * @param {Object} task - 定时任务对象\n * @returns {Promise<Object>} 新会话对象\n */\nexport async function createScheduledSession(task) {\n await init();\n const sessionId = generateSessionId();\n const newSession = {\n id: sessionId,\n title: task.name || t('session.scheduledSession'),\n // 注意:定时任务自建会话不写入占位配置,null 表示\"执行时继承全局\",\n // 避免 SW 环境下的默认值(state 占位)覆盖真实全局配置\n model: task.model || null,\n useTools: task.useTools != null ? task.useTools : null,\n enabledTools: null,\n agentId: task.agentId || null,\n temperature: task.temperature != null ? task.temperature : null,\n topP: task.topP != null ? task.topP : null,\n messageHistory: [],\n scrollPosition: 0,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n order: Date.now(),\n isGenerating: false,\n lastExecutionLog: [],\n scheduledTaskId: task.id,\n };\n\n await idb.putSession(newSession);\n return newSession;\n}\n"],"file":"session-store-DwXNnVF5.js"}