feat: add conversation search and usage dashboard
This commit is contained in:
94
.planning/advanced-conversation-search/findings.md
Normal file
94
.planning/advanced-conversation-search/findings.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# 高级会话检索发现与决策
|
||||
|
||||
## 用户边界
|
||||
|
||||
- 现有检索必须保留,不改输入行为和本地标题/项目/路径/ID 过滤。
|
||||
- 在现有检索框旁增加高级检索按钮。
|
||||
- 高级检索参照用户提供的暗色结果页,结合前一轮正文索引分析落地。
|
||||
|
||||
## 视觉论点
|
||||
|
||||
在不打扰现有侧栏检索的前提下,打开一层沉静、近乎无边框的检索工作台,用青蓝标题、暖黄命中标记和克制分隔线建立快速扫读节奏。
|
||||
|
||||
## 内容计划
|
||||
|
||||
1. 顶部:返回按钮、长查询输入、清空按钮和回车提示。
|
||||
2. 控制:相关优先/最新优先,整词/包含两组切换。
|
||||
3. 状态:结果数、耗时、索引构建/错误状态。
|
||||
4. 结果:会话标题、项目/角色来源、最多两段摘要、右对齐时间和细分隔线。
|
||||
5. 空态:未输入、无结果、索引构建中和查询失败。
|
||||
|
||||
## 交互论点
|
||||
|
||||
- 入口点击后使用 140ms 淡入和轻微上移,形成从侧栏到检索工作台的空间切换。
|
||||
- 查询采用约 200ms debounce;旧响应由 requestId 丢弃,结果更新只做轻微透明度过渡。
|
||||
- 点击摘要打开原会话,必要时补载历史页,定位后做一次短暂暖色高亮。
|
||||
|
||||
## 参考图量化
|
||||
|
||||
- 源图:733 × 673 RGBA PNG。
|
||||
- 顶栏约 58px;左右内容安全区约 46–48px。
|
||||
- 控制行位于顶栏下约 22px,采用低对比胶囊,不使用独立卡片。
|
||||
- 状态行与第一条结果间距约 18px;结果之间使用 1px 低对比分隔线。
|
||||
- 标题使用高饱和青蓝,正文接近中性白,命中使用暖黄底深色字,时间靠右且弱化。
|
||||
- 面板是单一连续表面,不叠加卡片阴影;参考图中的圆角仅属于外围窗口,不复制成结果卡片。
|
||||
|
||||
## 参考区域映射
|
||||
|
||||
| 参考区域 | 目标 DOM | CSS 责任 | 资产 |
|
||||
|---|---|---|---|
|
||||
| 顶部搜索栏 | `.advanced-search-header` | 高度、输入基线、返回/清空按钮 | 无 |
|
||||
| 排序/匹配切换 | `.advanced-search-toolbar` | 分段控件、选中态 | 无 |
|
||||
| 结果统计 | `#advanced-search-status` | 弱化状态文案 | 无 |
|
||||
| 结果列表 | `#advanced-search-results` | 连续流、分隔线、滚动 | 无 |
|
||||
| 命中标题/摘要 | `.result-title/.result-snippet` | 青蓝标题、暖黄 mark、截断 | 无 |
|
||||
| 侧栏入口 | `#advanced-search-open` | 紧邻现有输入、独立 tooltip | 无 |
|
||||
|
||||
## 资产台账
|
||||
|
||||
- 原始附件:`sessions/_attachments/237582ef-4157-4175-84fa-38629613df7f.png`
|
||||
- 归档:`.trellis/tasks/08-03-advanced-conversation-search/references/source-assets/advanced-search-reference.png`
|
||||
- SHA-256:`39e5b93d26deca64ea88a46b09bed7f7c47d2af733e8f2f857a9d35fc7895692`
|
||||
- 用途:仅作为视觉对照,不进入产品运行时。
|
||||
|
||||
## 已确认技术基线
|
||||
|
||||
- 当前 `session_list` 只携带元数据,完整消息按会话加载。
|
||||
- 当前可检索 user/assistant 正文约 4.26 MB,内存全量子串扫描约 0.98ms。
|
||||
- 项目运行于 Node 18,发布使用 Bun baseline 单文件;首期采用零依赖内存索引和派生缓存。
|
||||
|
||||
## 代码接入基线
|
||||
|
||||
- `codebase-memory-mcp` 的 `home-cc-web` 索引 ready(4417 节点、9212 边)。
|
||||
- 服务端模块统一使用 CommonJS 工厂函数,可新增 `lib/session-search-index.js` 并在 `server.js` 顶部 require。
|
||||
- WebSocket 消息 switch 在 `load_session/load_history_page/delete_session/rename_session` 附近,新增 `search_sessions` 可保持普通会话列表协议不变。
|
||||
- 前端 `handleServerMessage` 已集中处理 `session_info/session_history_chunk`,高级检索结果可新增独立 case;`openSession(sessionId, options)` 已支持 options,适合作为命中定位入口。
|
||||
- `markSessionMessageElement` 已给消息 DOM 写入 `data-message-index`;历史分页响应包含 `historyBaseIndex`,可以稳定定位旧消息。
|
||||
- `saveSession` 有大量调用者,索引更新必须按 sessionId debounce 并在成功写盘后触发,避免频繁同步重建。
|
||||
|
||||
## 跨层数据流
|
||||
|
||||
```text
|
||||
sessions/*.json
|
||||
→ SessionSearchIndex 规范化 user/assistant 文本
|
||||
→ 内存文档集合 + sessions/_search/index-v1.json 派生缓存
|
||||
→ search_sessions 请求验证
|
||||
→ session_search_results 受限摘要
|
||||
→ 高级检索结果流
|
||||
→ openSession + load_session(targetMessageIndex)
|
||||
→ data-message-index 定位与高亮
|
||||
```
|
||||
|
||||
- 服务端入口负责 query、limit、sort、matchMode 校验;前端只发送枚举值并丢弃过期 requestId。
|
||||
- 原始会话文件是唯一事实来源;缓存损坏只能降级/重建,不能反向覆盖会话。
|
||||
- Trellis backend/frontend 细则当前仍为占位模板,本次以现有 CommonJS、集中消息 switch、原生 DOM/CSS 和回归脚本的实际约定为准。
|
||||
|
||||
## 并行审计结论
|
||||
|
||||
- 高级检索面板放在 `.chat-main` 内并使用局部 absolute overlay;桌面保留侧栏,移动端点击入口后关闭抽屉。不要复用全局 modal overlay。
|
||||
- 现有 `sessionSearchQuery`、`syncSessionSearchUi`、`getSessionSearchText`、`sessionMatchesSearch`、`renderSessionList` 和原 input/Escape/clear 事件均保持原样;高级检索使用完全独立状态。
|
||||
- 搜索缓存的文件签名使用 dev/ino/size/mtimeMs;每次启动对账 root `*.json` 集合,原子保存导致 inode 变化时自然失效,删除项被 prune。
|
||||
- 初次构建和变化扫描分批让出事件循环;查询只访问内存文档,不在请求路径解析全部 JSON。
|
||||
- 高级面板 z-index 位于聊天内容之上、全局 modal 之下;结果区自身滚动,避免落进 `.messages-wrap` 的 overflow 裁切。
|
||||
- 精确定位必须返回 `sessionId + messageIndex`;前端在 `renderMessages/prependHistoryMessages` 后查找 `data-message-index`,服务端按 `targetMessageIndex` 增加首次加载的历史预取块。
|
||||
- 修改 `style.css/app.js` 后同步推进 `index.html` cache-bust,并覆盖 dark/gilded/wasteland 与 768px 移动端。
|
||||
26
.planning/advanced-conversation-search/progress.md
Normal file
26
.planning/advanced-conversation-search/progress.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 高级会话检索进展
|
||||
|
||||
- 2026-08-03:读取并启用 create-cc-web-theme、frontend-skill、planning-with-files、todo-list-csv。
|
||||
- 2026-08-03:创建并启动 Trellis 任务 `08-03-advanced-conversation-search`。
|
||||
- 2026-08-03:归档参考图,确认源图与归档 hash 一致。
|
||||
- 2026-08-03:建立 10 步 TODO CSV 与对话计划,当前处于规格与基线阶段。
|
||||
- 2026-08-03:首次派发后端审计子代理因 full-history 与 agent_type 参数冲突失败,已记录并改用无历史完整提示。
|
||||
- 2026-08-03:确认 codebase-memory 索引 ready,并定位服务端模块、WebSocket、前端消息分发和历史定位主链路。
|
||||
- 2026-08-03:读取 Trellis backend/frontend 与跨层指南;细则为空,已按实际代码模式记录数据流和边界责任。
|
||||
- 2026-08-03:wait_agent 首次使用低于最小等待窗口,已记录并改用 10 秒。
|
||||
- 2026-08-03:独立计划审查通过,无阻塞问题;已将协议上限、缓存校验和 messageIndex 定位补入主计划。
|
||||
- 2026-08-03:后端/前端并行只读审计完成;汇总独立协议、文件签名缓存、局部 overlay、主题和命中定位边界。
|
||||
- 2026-08-03:完成 TODO 第 1 步,进入索引模块实现。
|
||||
- 2026-08-03:新增 `lib/session-search-index.js`,完成版本化 0600 派生缓存、文件指纹校验、分批构建、增量 upsert/remove、相关/最新与包含/整词检索。
|
||||
- 2026-08-03:服务端接入 `search_sessions → session_search_results/session_search_error`,限制 query≤200、limit≤50、120ms 单连接频率,并保持 `session_list` 原协议不变。
|
||||
- 2026-08-03:`saveSession`、重命名和删除链路已同步索引;查询前 flush pending upsert,避免 250ms debounce 窗口内读取旧索引。
|
||||
- 2026-08-03:侧栏原检索 DOM 属性和 input/Escape/clear 事件保持不变,仅增加相邻 `#advanced-search-open`;高级工作台使用独立状态和 `.chat-main` 局部 overlay。
|
||||
- 2026-08-03:完成安全 DOM 高亮、requestId 旧响应隔离、200ms 防抖、结果流、排序/匹配切换及 `sessionId + messageIndex` 定位。
|
||||
- 2026-08-03:目标命中通过 `load_session.targetMessageIndex` 计算额外预取块,180 条消息夹具可补载到 `historyBaseIndex=0`。
|
||||
- 2026-08-03:新增 `advanced-session-search` 专项回归,覆盖敏感内容排除、整词检索、协议隔离和旧历史定位;语法、diff、gilded、wasteland、全量回归通过。
|
||||
- 2026-08-03:真实 Chromium 验收 1672×941、1440×900、390×844;三个视口横向溢出均为 0,结果区 `overflow-y:auto`,控制台错误为 0。
|
||||
- 2026-08-03:视觉实测为深色连续表面、青蓝标题 `rgb(0,164,223)`、暖黄命中 `rgb(242,211,107)`;桌面侧栏保留,移动侧栏抽屉关闭后面板占满主画布。
|
||||
- 2026-08-03:专项浏览器点击结果确认面板关闭并生成 `data-message-index="0"` 的目标高亮;临时实例使用 18082 端口,验收后已停止,未重启生产 ccweb。
|
||||
- 2026-08-03:完成后尝试按项目约定刷新 codebase-memory 全量索引;`index_repository` 与后续 `index_status` 均返回 `Transport closed`,已降级为本地语法、专项/全量回归与 diff 核验,不影响运行时代码交付。
|
||||
- 2026-08-03:根据用户截图复核 wasteland 侧栏几何,将搜索框的 8px 外距提升到行容器,并把高级按钮统一为 36×36;真实 Chromium bbox 为搜索框与按钮 `y=66 / h=36 / centerY=84`,中心差 0。
|
||||
- 2026-08-03:对齐调整后 cache-bust 更新为 `20260803-advanced-search-align`;advanced-session-search、wasteland-theme 与全量 regression 再次通过。
|
||||
62
.planning/advanced-conversation-search/task_plan.md
Normal file
62
.planning/advanced-conversation-search/task_plan.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 高级会话检索落地计划
|
||||
|
||||
## Goal
|
||||
|
||||
保留现有侧栏检索全部行为,在旁边新增高级检索入口,落地可检索用户/助手消息正文、展示命中摘要并定位原消息的独立检索工作台。
|
||||
|
||||
## Current Phase
|
||||
|
||||
Complete
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 1: 规格与基线
|
||||
|
||||
- [x] 归档参考图并记录哈希
|
||||
- [x] 审计相关实现、测试与主题边界
|
||||
- [x] 完成计划审查
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 2: 服务端检索能力
|
||||
|
||||
- [x] 实现零依赖内存文档索引和缓存
|
||||
- [x] 缓存按 version + 文件 dev/ino/size/mtime 校验,缺失或损坏时后台分批重建
|
||||
- [x] 接入会话生命周期
|
||||
- [x] 接入 WebSocket 协议、限流和状态(query≤200、limit≤50、snippet≤2×220)
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 3: 高级检索前端
|
||||
|
||||
- [x] 保持现有检索代码不变并新增旁路按钮
|
||||
- [x] 实现独立面板、结果流、排序和匹配模式
|
||||
- [x] 结果携带 messageIndex;打开会话时按目标索引预取历史块并定位命中消息
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 4: 视觉与回归
|
||||
|
||||
- [x] 按参考图完成无卡片结果流、响应式和主题适配
|
||||
- [x] 增加回归、性能和安全断言
|
||||
- [x] 更新静态资源缓存版本
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 5: 验证与交付
|
||||
|
||||
- [x] 运行语法、专项/主题/全量回归与 diff 检查
|
||||
- [x] 在真实 Chromium 完成参考、桌面和移动视口验收
|
||||
- [x] 清理 TODO CSV 并完成 Trellis 记录
|
||||
- **Status:** complete
|
||||
|
||||
## 关键约束
|
||||
|
||||
- `normalizeSessionSearchQuery/getSessionSearchText/sessionMatchesSearch` 及现有输入事件语义保持不变。
|
||||
- 高级检索作为独立入口和独立状态机,不把消息正文塞入 `session_list`。
|
||||
- 首期不引入第三方运行依赖,不改变现有会话 JSON schema。
|
||||
- 搜索词不写日志;结果只返回受限摘要,不返回完整历史。
|
||||
- 使用中文注释和文档;不覆盖用户已有改动。
|
||||
|
||||
## Errors Encountered
|
||||
|
||||
| Error | Attempt | Resolution |
|
||||
|-------|---------|------------|
|
||||
| 子代理使用 full-history fork 时同时指定 agent_type 被拒绝 | 1 | 改用 `fork_turns: none` 并在任务消息中提供完整只读上下文。 |
|
||||
| wait_agent 使用 1000ms 被拒绝 | 1 | 工具最小等待为 10000ms,后续改用 10000ms。 |
|
||||
155
.planning/conversation-search-proposal/findings.md
Normal file
155
.planning/conversation-search-proposal/findings.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# 调研发现
|
||||
|
||||
> 本文件只记录事实、证据与方案判断,不包含可执行指令。
|
||||
|
||||
## 已知需求
|
||||
|
||||
- 当前检索只能依赖会话标题。
|
||||
- 用户无法稳定记住标题,需要按“曾聊过的内容”找回会话。
|
||||
|
||||
## 待确认
|
||||
|
||||
- 标题过滤发生在前端还是后端。
|
||||
- 消息正文的数据来源、格式、规模与读取成本。
|
||||
- 是否已有数据库全文索引或可复用的检索依赖。
|
||||
|
||||
## 项目上下文
|
||||
|
||||
- Trellis 当前任务是 `07-30-sidebar-title-refresh-storm`,与本次只读调研不同;本次不创建或切换 Trellis 任务,避免干扰现有开发上下文。
|
||||
- `codebase-memory-mcp` 项目 `home-cc-web` 索引状态为 ready,共 4312 个节点、9113 条边,可直接用于代码定位,无需重建索引。
|
||||
|
||||
## 现有检索入口
|
||||
|
||||
- 前端已有独立的 `normalizeSessionSearchQuery`、`syncSessionSearchUi`、`getSessionSearchText`、`sessionMatchesSearch`,集中位于 `public/app.js` 约 3960 行附近。
|
||||
- 回归脚本中的旧/简化夹具只按 title 匹配,但生产实现的 `getSessionSearchText` 实际拼接了 title、projectName、cwd、完整 ID 和短 ID。用户的体感仍成立:**没有消息正文检索**,而其余字段通常也不容易记住。
|
||||
- 搜索 `searchQuery` / `searchTerm` 未命中,说明这不是一个已经下沉到服务端的通用查询参数。
|
||||
- 后端存在 `listConversationSummaries`、`sendSessionList`、`handleLoadSession` 等候选链路,下一步需确认列表摘要是否携带正文、消息是否仅在打开会话时加载。
|
||||
|
||||
## 列表与存储初步结论
|
||||
|
||||
- `sessionMatchesSearch` 由 `renderSessionList` 调用;检索完全发生在会话列表渲染期。
|
||||
- `listConversationSummaries` 会遍历 `SESSIONS_DIR/*.json`,通过 `loadSessionMetaFromFile` 只提取元数据,返回字段包含 title、agent、status、updatedAt、cwd、projectName 等,但不含消息正文或正文摘要。
|
||||
- `sendSessionList` 同样依赖 `loadSessionMetaFromFile`,说明普通侧边栏列表和 MCP 会话列表都刻意走轻量元数据路径。
|
||||
- 完整会话由 `loadSession(id)` 读取并解析 JSON,`handleLoadSession` 在打开单个会话时才发送历史消息;因此把所有历史消息塞进现有 session_list 再由前端检索,会破坏当前“轻量列表、按需加载”的架构边界。
|
||||
- `loadSessionMetaFromFile` 已针对超大 JSON 做头尾预览和大小阈值处理,侧面说明会话文件可能很大,不能在每次输入搜索词时全量扫描解析所有 JSON。
|
||||
|
||||
## 规模与边界常量
|
||||
|
||||
- 默认 `SESSIONS_DIR` 是项目下的 `sessions/`,可通过 `CC_WEB_SESSIONS_DIR` 覆盖。
|
||||
- 默认单会话保存上限约 10 MiB、加载上限约 32 MiB;超过 512 KiB 时列表元数据不再完整解析文件,而是只读约 128 KiB 头尾预览。
|
||||
- 默认持久化最多 180 条消息、单条消息正文最多约 96 KiB。即使消息条数受限,全部会话逐文件全文扫描仍会产生明显同步 I/O 和 JSON 解析开销。
|
||||
- 会话读写目前是 JSON 文件制,不是数据库;因此推荐方案应新增旁路搜索索引,不能把搜索做成 `sendSessionList` 内的全文件扫描。
|
||||
|
||||
## 当前真实数据规模(2026-08-03,只输出聚合值)
|
||||
|
||||
- 133 个会话 JSON,总计约 81.8 MB;文件中位数约 337 KB,P95 约 2.63 MB,最大约 4.09 MB。
|
||||
- 共 2197 条持久化消息;单会话消息数中位数 7、P95 67、最大 128。
|
||||
- 现存消息 content 全部是字符串,角色分布为 user 1133、assistant 979、system 85,解析错误 0。
|
||||
- 当前 `package.json` 唯一运行依赖是 `ws`,没有 SQLite、全文检索或分词库。
|
||||
- 数据量尚未大到需要 Elasticsearch/Meilisearch 这类独立服务,但已经足以让“每次键入都同步读取并解析全部会话文件”的朴素方案产生明显卡顿。
|
||||
|
||||
## 实测检索基线(2026-08-03)
|
||||
|
||||
- 排除 system 后,可检索的 user/assistant 消息为 2112 条,规范化正文总量约 4.26 MB。
|
||||
- 一次性读取全部 133 个会话并解析/规范化正文约 867 ms;所以不能在搜索请求内现读原文件,但可以后台建索引。
|
||||
- 正文已在内存时,对全部 2112 条消息执行一次最坏情况的精确子串扫描平均约 0.98 ms。
|
||||
- 结论:当前规模最合适的不是外部搜索服务或原生数据库,而是“服务端内存文档索引 + 持久化增量缓存”。它天然支持中文子串、依赖为零,未来正文规模大两个数量级时再切换倒排/FTS 后端。
|
||||
|
||||
## 运行时兼容约束
|
||||
|
||||
- 开发/普通启动基线是 Node 18.19;发布包用 `bun build --compile --target=bun-linux-x64-baseline` 生成 CentOS 7 兼容单文件。
|
||||
- 当前代码只在删除 Codex 本地会话时 best-effort 调用宿主机 `sqlite3` CLI,不能把它视为必备依赖。
|
||||
- `node:sqlite` 不适用于 Node 18,`bun:sqlite` 又无法覆盖 Node 启动路径;`better-sqlite3` 等原生扩展会增加 Bun 单文件和老 glibc 发布风险。因此 SQLite FTS5 不应作为第一阶段主路径。
|
||||
|
||||
## 现有 UI 行为
|
||||
|
||||
- 搜索输入每次触发 `input` 都立即调用 `renderSessionList`,无 debounce、无异步状态。
|
||||
- 搜索时现有逻辑已经自动展开折叠项目和旧会话,这一交互可复用。
|
||||
- 搜索命中只决定“显示/隐藏会话”,当前列表项没有命中片段、命中字段、匹配条数或定位消息能力。
|
||||
|
||||
## 接入点与一致性
|
||||
|
||||
- WebSocket 服务端已有按 `msg.type` 分发的 switch,可新增 `search_sessions`;前端 `handleServerMessage` 可新增 `session_search_results` / `session_search_status`。
|
||||
- `saveSession` 是所有会话持久化的中心入口,拥有 26 个直接调用者;适合作为“按会话 debounce 后增量 upsert 索引”的统一钩子,但不能每次保存都同步重建。
|
||||
- 删除、重命名、新建分别有集中处理函数,可分别触发 remove、metadata update、initial upsert。
|
||||
- 现有回归测试里的搜索夹具仍只匹配 title,与生产 `getSessionSearchText` 不一致;实现时应新增独立全文检索契约测试,并修正夹具,避免测试继续掩盖真实语义。
|
||||
|
||||
## 命中定位可行性
|
||||
|
||||
- 搜索结果可以返回持久化数组中的 `messageIndex`,现有 DOM 消息节点已经写入 `data-message-index`。
|
||||
- 旧消息已有 `load_history_page(before)` 分页接口,可以按目标 index 逐页补载;因此“点击结果 → 打开会话 → 自动加载目标页 → 滚动并高亮命中消息”不需要改会话存储格式。
|
||||
- 现有 `createSessionListItem` 点击只调用 `openSession(session.id)`;可扩展为接受可选 searchMatch,而不影响普通列表点击。
|
||||
|
||||
## 消息可索引范围
|
||||
|
||||
- `normalizeSession` 保留 `messages` 数组;持久化链路会限制消息数、正文长度和工具调用体积。
|
||||
- 第一版应只索引 title、projectName/cwd、用户消息和助手可见文本;默认排除 system 文本、tool input、tool result、附件二进制/元数据,减少噪声、索引体积和敏感信息暴露。
|
||||
|
||||
## 候选方案比较
|
||||
|
||||
1. 前端预加载全部消息后过滤:实现表面简单,但会把当前约 4.26 MB 且持续增长的正文发给浏览器,破坏轻量列表和按需加载,不推荐。
|
||||
2. 每次查询直接遍历 `sessions/*.json`:无需新文件,但当前一次全量解析已约 867 ms,会阻塞 Node 事件循环,不可接受。
|
||||
3. 服务端内存文档索引 + 持久化增量缓存:当前最坏正文扫描约 0.98 ms,中文精确子串天然可用,无新增依赖;推荐。
|
||||
4. SQLite FTS5 / FlexSearch / Meilisearch:倒排检索和模糊能力更强,但当前数据规模用不上;SQLite 与 Node 18/Bun baseline 双运行时存在部署成本,外部服务还有运维和隐私成本,保留为规模升级路径。
|
||||
|
||||
## 推荐架构
|
||||
|
||||
- 新增独立 `SessionSearchIndex` 模块,接口固定为 `initialize/upsert/remove/search/status`,具体后端首期使用内存文档集合,未来可替换而不改 WebSocket/UI 协议。
|
||||
- 索引粒度为“消息文档”:sessionId、messageIndex、role、timestamp、原始 snippet 文本、规范化文本;另保存每会话 title/project/cwd/id 元数据。
|
||||
- 规范化使用 Unicode NFKC、lowercase、合并空白;首期做精确短语 + 多词 AND,不做拼音、向量语义或重型分词。
|
||||
- 缓存存放到 `sessions/_search/index-v1.json`,与原始会话处于同一数据权限边界;缓存是派生数据,版本不匹配、损坏或缺失时自动后台重建。
|
||||
- 启动先载入缓存,再通过文件 size + mtime 校验;只解析新增/变化的会话并移除已删除项。首次全量建索引异步分批执行,不阻塞服务监听。
|
||||
- `saveSession` 成功后按 sessionId debounce 增量 upsert;rename 立即更新元数据;delete 立即 remove;缓存写盘合并并使用原子替换。
|
||||
- 搜索请求只访问内存索引,绝不在请求路径读取全部会话文件。
|
||||
|
||||
## 协议与结果形状
|
||||
|
||||
- 请求:`search_sessions { requestId, query, agent, limit }`;query 最大 200 字符,limit 默认 30、最大 50。
|
||||
- 响应:`session_search_results { requestId, query, indexState, tookMs, results }`。
|
||||
- 单条结果只返回 sessionId、title、projectName、updated、score、matchType、matchedMessageCount,以及最多 2 个 200 字符左右的 snippet;不返回完整历史。
|
||||
- 前端使用 requestId 丢弃乱序旧响应;输入 debounce 建议 160–220 ms。
|
||||
|
||||
## 排序与展示
|
||||
|
||||
- 搜索模式改为扁平相关性列表,清空查询后恢复现有按项目/置顶分组;避免“置顶/项目顺序”压过真正的命中相关性。
|
||||
- 分值优先级:标题前缀 > 标题包含 > 用户消息精确短语 > 助手消息精确短语 > 多词 AND > 项目/cwd/id;最后只给近期和置顶小幅加分。
|
||||
- 每条结果展示项目、命中来源(标题/用户消息/助手回复/路径)、相对时间、命中片段和命中数;关键词高亮必须基于文本节点,不能拼接未转义 HTML。
|
||||
- 一个字符仅做现有元数据过滤;至少两个字符才发起正文搜索,避免单字产生大量无意义结果。
|
||||
- 索引构建时继续提供标题/项目本地过滤,并显示“正在建立内容索引 x%”;完成后自动重跑当前查询。
|
||||
|
||||
## 分期建议
|
||||
|
||||
- P0(核心找回):正文索引、增量缓存、WebSocket 查询、相关性列表、命中片段、索引状态、独立回归测试。
|
||||
- P1(精准定位):点击内容命中后按 messageIndex 打开会话、自动补载历史页、滚动并短暂高亮目标消息。
|
||||
- P2(规模升级,可选):增加时间/项目/角色筛选;当可检索正文超过 100 MB、会话超过 5000 或 p95 查询持续超过 50 ms 时,再切换纯 JS 倒排或 FTS 后端。
|
||||
|
||||
## 安全与可运维性
|
||||
|
||||
- 默认排除 system、tool input、tool result 和附件;搜索词不写日志,日志只记录 tookMs、文档数、缓存命中/重建状态。
|
||||
- 缓存文件权限应收紧到当前用户,写入采用临时文件 + 原子 rename;缓存删除不会丢业务数据,只会触发重建。
|
||||
- 服务端限制查询长度、结果数和 snippet 长度;前端高亮不得使用未经 escape 的 `innerHTML`,防止历史消息形成持久型 XSS。
|
||||
|
||||
## 预计实施范围
|
||||
|
||||
- 新增 `lib/session-search-index.js`:索引构建、缓存、规范化、排序、snippet 和状态。
|
||||
- 修改 `server.js`:初始化索引、生命周期钩子、WebSocket 请求/响应、状态与限流。
|
||||
- 修改 `public/app.js`:debounce、异步请求状态、乱序响应保护、扁平结果渲染;P1 增加命中定位。
|
||||
- 修改 `public/index.html` / `public/style.css`:占位文案、索引状态、snippet/高亮,并覆盖移动端和现有主题。
|
||||
- 修改 `scripts/regression.js`:新增独立搜索契约、增量一致性、缓存恢复与前端安全断言。
|
||||
- 不修改现有会话 JSON schema,不引入第三方运行依赖,不要求一次性数据迁移。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 能用只出现在历史用户消息或助手回复中的中英文片段找到正确会话;标题、项目、路径、ID 的原能力不回退。
|
||||
- system/tool/附件内容默认不产生命中;搜索响应不包含完整会话正文。
|
||||
- 新建/新增消息在持久化后 2 秒内可检索,重命名立即生效,删除后不再命中;重启、缓存缺失、缓存损坏均能自动收敛。
|
||||
- 搜索结果按相关性而非项目顺序展示,snippet 正确转义;乱序响应不会覆盖新查询。
|
||||
- 当前规模 p95 服务端查询低于 50 ms,WebSocket 单次响应受 50 条和 snippet 上限约束;全量重建不阻塞正常会话操作。
|
||||
- `npm run regression` 通过,并对 Node 启动与 Bun baseline 单文件构建分别验证。
|
||||
|
||||
## 工作量与风险
|
||||
|
||||
- P0 属于中等改动,预计 1.5–2 个开发日;P1 命中定位约 0.5–1 个开发日。主要成本在异步状态、缓存一致性和回归覆盖,不在搜索算法。
|
||||
- 最大风险是索引陈旧和构建期阻塞;通过中心 save 钩子 + 启动 mtime/size 对账 + 分批异步重建解决。
|
||||
- 缓存会复制约 4.26 MB 当前正文,需和 sessions 同权限、禁止误打进空白发布包或日志;它不是唯一数据源,可随时重建。
|
||||
- 活跃助手流式内容可能要到下一次持久化才可检索;首期以历史找回为目标,这个延迟可接受并应在测试中明确。
|
||||
19
.planning/conversation-search-proposal/progress.md
Normal file
19
.planning/conversation-search-proposal/progress.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 进展日志
|
||||
|
||||
- 2026-08-03:启动只读代码审计;已读取 planning-with-files 规则并完成会话恢复检查。
|
||||
- 2026-08-03:建立隔离研究目录,避免干扰仓库中已有计划。
|
||||
- 2026-08-03:读取 Trellis 工作流;确认现有 current-task 与本调研无关,保持不变。
|
||||
- 2026-08-03:确认 codebase-memory 索引可用,无需重建。
|
||||
- 2026-08-03:定位前端搜索函数,确认当前实现只匹配 `session.title`。
|
||||
- 2026-08-03:定位会话列表与完整会话加载边界,确认列表接口不含正文,完整消息按会话加载。
|
||||
- 2026-08-03:核对生产搜索字段与容量常量;修正“仅标题”为“标题/项目/路径/ID,但无正文”。
|
||||
- 2026-08-03:完成当前会话文件与消息规模聚合,确认 133 会话/约 81.8 MB/2197 消息,现有依赖无搜索能力。
|
||||
- 2026-08-03:完成正文体量和扫描基准;确认内存正文扫描约 0.98 ms,后台建索引约 0.87 s。
|
||||
- 2026-08-03:核对 Node 18 + Bun baseline 单文件约束,排除首期原生 SQLite 依赖。
|
||||
- 2026-08-03:定位 WebSocket 协议、前端消息分发和新建/保存/重命名/删除生命周期接入点。
|
||||
- 2026-08-03:验证按 messageIndex 定位旧消息可复用现有历史分页和 DOM 索引标记。
|
||||
- 2026-08-03:完成四类架构对比,确定“服务端内存文档索引 + 持久化增量缓存”为首期方案。
|
||||
- 2026-08-03:定义索引边界、WebSocket 协议、排序、UI、增量生命周期和升级阈值。
|
||||
- 2026-08-03:核验源码行号,完成实施文件、验收指标、工作量和风险说明。
|
||||
- 2026-08-03:本轮只读方案审计完成;未修改业务代码、未重启服务。
|
||||
- 2026-08-03:修正规划完成检查格式;第一次复检因错误说明触发标题匹配而误计阶段,已记录并改写。
|
||||
62
.planning/conversation-search-proposal/task_plan.md
Normal file
62
.planning/conversation-search-proposal/task_plan.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 会话内容检索优化方案
|
||||
|
||||
## Goal
|
||||
|
||||
基于 cc-web 当前代码与存储方式,给出可按会话消息正文找回历史会话的可落地方案;本轮只调研和设计,不修改产品代码。
|
||||
|
||||
## Current Phase
|
||||
|
||||
Phase 5(已完成)
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 1: 项目上下文与边界
|
||||
|
||||
- [x] 读取 Trellis 工作流
|
||||
- [x] 建立隔离研究目录
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 2: 现有检索链路
|
||||
|
||||
- [x] 确认 codebase-memory 索引
|
||||
- [x] 定位前端过滤与后端列表接口
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 3: 数据与性能约束
|
||||
|
||||
- [x] 核查消息结构与持久化限制
|
||||
- [x] 聚合真实数据规模并执行只读基准
|
||||
- [x] 验证历史消息定位能力
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 4: 架构比较与推荐
|
||||
|
||||
- [x] 比较浏览器过滤、实时文件扫描、内存索引和 FTS/外部服务
|
||||
- [x] 确定服务端内存文档索引 + 持久化增量缓存
|
||||
- **Status:** complete
|
||||
|
||||
### Phase 5: 交付方案
|
||||
|
||||
- [x] 定义协议、UI、排序、增量生命周期和分期
|
||||
- [x] 整理实施范围、验收标准、风险与成本
|
||||
- [x] 核验源码行号和只读基准
|
||||
- **Status:** complete
|
||||
|
||||
## 关键约束
|
||||
|
||||
- 本轮不修改业务代码、不重启服务。
|
||||
- 优先使用 codebase-memory-mcp 理解代码,rg/sed 只校验行号与配置。
|
||||
- 方案兼顾已有大量会话、中文检索、历史数据迁移和持续增量索引。
|
||||
|
||||
## 决策
|
||||
|
||||
- 首期采用服务端内存文档索引 + `sessions/_search/` 持久化增量缓存。
|
||||
- 保持现有轻量 `session_list`,正文只在命中时返回受限 snippet。
|
||||
- 不首期引入 SQLite/原生依赖或外部搜索服务。
|
||||
|
||||
## Errors Encountered
|
||||
|
||||
| Error | Attempt | Resolution |
|
||||
|-------|---------|------------|
|
||||
| 完成检查显示 `0/0 phases` | 1 | 原计划使用中文勾选列表,检查脚本只识别标准 Phase 标题与状态字段;已改用标准格式并显式传入计划路径复检。 |
|
||||
| 完成检查显示 `5/6 phases` | 2 | 错误说明本身包含了检查器的标题匹配字面量,被误计为第六阶段;已改写错误说明。 |
|
||||
102
.planning/usage-statistics-dashboard-plan/findings.md
Normal file
102
.planning/usage-statistics-dashboard-plan/findings.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# 调研结论:cc-web 使用统计看板
|
||||
|
||||
## 用户要求
|
||||
|
||||
- 当前只需要可执行计划,不实施产品代码。
|
||||
- 看板必须不影响现有聊天、会话、检索和工具调用功能。
|
||||
- 指标必须有明确口径,不能使用含糊的“活跃会话”。
|
||||
- Skill 只统计显式 `$skill` 使用,不宣称能够监控实际读取或执行。
|
||||
- 用户已授权开始实施,并要求先完成整体功能,再适配所有现有主题。
|
||||
- 用户指定统计入口位于侧栏底部:会话列表下方、现有 `CC-Web` 设置入口同一区域,而不是会话列表内部。
|
||||
- 入口参考图为 `sessions/_attachments/750cb7a2-a109-44a9-ae3d-6ca352d4aba0.jpg`;图中底部操作区在会话列表滚动区域之外。
|
||||
|
||||
## 现有数据能力
|
||||
|
||||
- 会话数据保存在 `sessions/*.json`。
|
||||
- 用户消息可持久化 `composerMentions`,可识别显式 `$skill` mention。
|
||||
- Codex App 的 MCP 调用会规范化到 assistant message 的 `toolCalls[]`,包含 `server`、`tool`、`status`。
|
||||
- 现存 MCP 工具调用没有独立时间戳;历史统计只能暂时使用所属 assistant 消息完成时间。
|
||||
- 会话持久化有消息数和每条消息工具调用数上限,因此看板只能统计“当前保留数据”,不能声称是永久全量审计。
|
||||
- 当前会话主创建字段为 `created`;134 个会话均有 `created/updated`,当前 2,226 条保留消息均有 `timestamp`。
|
||||
- 当前扫描得到 1,745 次 MCP 调用,`completed=1645`、`failed=100`;失败调用同样可能 `done=true`,失败口径必须读取 `meta.status`。
|
||||
- 显式 Skill mention 的稳定判定是 `composerMentions[].kind === 'skill'`,不能扫描消息正文中的 `$xxx`。
|
||||
- `crossConversation` 可区分跨会话自动消息;界面使用“直接发送消息”和“跨会话消息”,避免把前者绝对命名为人工消息。
|
||||
|
||||
## 已有数据扫描结果
|
||||
|
||||
- 134 个会话 JSON,总量约 82 MB。
|
||||
- 1,678 次 MCP 调用,`server/tool` 解析率 100%。
|
||||
- MCP 状态:`completed=1578`,`failed=100`。
|
||||
- 显式 Skill mention 共 7 次。
|
||||
- 一次全量扫描约 0.93 秒,峰值 RSS 约 116 MB。
|
||||
|
||||
## 架构判断
|
||||
|
||||
- 82 MB 数据量下,全量扫描可以用于一次性回填或维护操作。
|
||||
- 不应让每次看板刷新都全量解析所有会话文件,否则数据增长后会和聊天服务争用 CPU、内存和磁盘 IO。
|
||||
- 最稳妥的结构是:独立索引文件、异步更新、独立查询协议、独立前端工作区。
|
||||
- 统计是旁路只读能力,不进入消息发送和会话写入的同步关键路径。
|
||||
- 统计索引采用懒加载:第一次打开看板才加载/回填,未使用看板时不增加服务启动成本。
|
||||
- 索引初始化后可防抖增量更新;调度与删除必须内层 `try/catch`,不能让统计异常进入 `saveSession` 或删除会话的主错误分支。
|
||||
- 统计响应只返回聚合值、工具名、Skill 名和会话元数据,不返回消息正文、MCP 参数或工具结果。
|
||||
|
||||
## 页面范围
|
||||
|
||||
- 顶部:时间范围、本周、本月、自定义范围、刷新。
|
||||
- 首行指标:新建会话、发送消息、MCP 调用、MCP 失败、Skill 显式使用。
|
||||
- 中部:按日趋势;MCP 工具使用明细。
|
||||
- 下部:MCP 状态分布;Skill 显式使用排行;最近会话明细。
|
||||
- MCP 工具明细行可进入该工具的调用明细;该交互属于网页实现,不需要在生图提示词中逐字描述。
|
||||
- 看板作为 `.chat-main` 内局部覆盖工作区,聊天 DOM 保持挂载;高级检索与看板互斥打开。
|
||||
- 入口必须使用独立 `.usage-dashboard-open`,不能复用 `.settings-btn`,否则 Wasteland 的齿轮伪元素会污染统计按钮。
|
||||
- 当前共有 11 个主题 ID;基础样式使用语义 token,专属修正只需要 coolvibe、共享暗色组、gilded 和 wasteland。
|
||||
|
||||
## 代码落点依据
|
||||
|
||||
- `public/index.html` 已有 sidebar 与 `main.chat-main`,看板可以作为 chat-main 内独立工作区,而不必重做应用外壳。
|
||||
- `server.js` WebSocket 分发已有独立消息类型模式,可新增统计查询类型而不改变现有协议。
|
||||
- `scripts/regression.js` 已覆盖高级检索的独立入口和独立状态模式,统计看板可沿用相同隔离策略。
|
||||
- codebase-memory 项目 `home-cc-web` 索引状态为 ready(4,455 nodes / 9,320 edges)。
|
||||
- `saveSession()` 是高入度核心写入函数,统计逻辑不得直接接入其同步调用链;旁路索引应在看板查询或独立后台任务中刷新。
|
||||
- 用户和 assistant 消息均有 `timestamp`;Codex App steer 用户消息也会持久化 `composerMentions`。
|
||||
- MCP 调用继续从 assistant message 的 `toolCalls` 读取,`ensureToolCall()` 负责归一化名称、kind、meta 和状态更新。
|
||||
|
||||
## 风险与控制
|
||||
|
||||
| 风险 | 控制方式 |
|
||||
|---|---|
|
||||
| 看板查询拖慢聊天服务 | 使用增量索引;请求限时;禁止请求时全量扫描 |
|
||||
| 索引与会话数据不一致 | 保存源文件指纹;可重建;界面显示统计更新时间 |
|
||||
| 历史 MCP 时间不准确 | 明示按 assistant 消息时间归属;不展示伪精确耗时 |
|
||||
| Skill 指标被误解 | 指标名称固定为“Skill 显式使用” |
|
||||
| 前端状态污染聊天 | 独立状态对象、DOM 根节点、样式命名空间和关闭恢复流程 |
|
||||
| 新功能引入回归 | 功能开关、合同测试、现有完整回归、灰度启用 |
|
||||
| 现有文件已有未提交修改 | 只在精确区块追加,不重排或覆盖高级会话检索改动;以任务开始时 diff 为基线 |
|
||||
|
||||
## 计划审查记录
|
||||
|
||||
- 第一次审查发现原计划缺少“全部现有主题逐一适配”的独立阶段。
|
||||
- 已新增 Phase 5 和硬性主题验收门槛,第二次审查通过。
|
||||
- 复审确认现有会话主创建字段为 `created`;实施以此为主,`createdAt` 只作历史兼容兜底。
|
||||
- 统计响应需携带 `schemaVersion`,性能验收已补充明确阈值。
|
||||
|
||||
## 资源
|
||||
|
||||
- 参考图:`sessions/_attachments/67a5f5ae-ea62-4c12-b723-deb9adb98c2f.png`
|
||||
- 会话存储与 WebSocket:`server.js`
|
||||
- Codex App 工具调用归一化:`lib/codex-app-runtime.js`
|
||||
- 页面外壳:`public/index.html`
|
||||
- 现有前端状态与交互:`public/app.js`
|
||||
- 回归测试:`scripts/regression.js`
|
||||
|
||||
## 实施与验收结论
|
||||
|
||||
- 统计索引在第一次打开看板时才构建;未初始化时保存会话不会安排统计更新。
|
||||
- 查询响应包含 `schemaVersion` 和 `[from,to)` 语义,不包含消息正文、MCP 参数或结果。
|
||||
- `usage_stats_query` 使用独立 requestId,真实协议回归确认不会新增 `session_list`。
|
||||
- `CC_WEB_USAGE_STATISTICS=0` 会关闭鉴权 feature flag,并让查询返回 `disabled`,不影响服务其他能力。
|
||||
- 真实数据最终性能:83,335,652 字节、134 个会话,回填 1,146.68 ms,查询 P95 51.79 ms,峰值 RSS 119,476 KB,缓存 394,629 字节。
|
||||
- 浏览器验收:Chrome Headless 151.0.7922.71,视口为 1440×900、1024×768、768×1024、390×844、360×800;11 个主题共 55 组。
|
||||
- 页面级横向溢出均为 0;窄屏 MCP 表格按设计在 `.usage-dashboard__table-wrap` 内局部滚动。
|
||||
- Wasteland 的 `.usage-dashboard-open` 没有继承 `.settings-btn::before`;移动端入口为 44×44,桌面为 34×34。
|
||||
- 浏览器动态状态已覆盖 loading、MCP 明细、空态和错误态;Wasteland 根看板使用实色背景,避免下层聊天视觉透出。
|
||||
87
.planning/usage-statistics-dashboard-plan/progress.md
Normal file
87
.planning/usage-statistics-dashboard-plan/progress.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 进度记录:cc-web 使用统计看板计划
|
||||
|
||||
## 2026-08-03
|
||||
|
||||
### Phase 1:边界与统计口径确认
|
||||
|
||||
- **状态:** complete
|
||||
- 已确认当前只产出计划,不修改产品代码。
|
||||
- 已确认可靠指标及其时间归属。
|
||||
- 已排除“活跃会话”和“Skill 实际读取次数”等不可靠指标。
|
||||
- 已记录“不影响现有功能”的硬性边界。
|
||||
|
||||
### Phase 2 至 Phase 6:实施与验收
|
||||
|
||||
- **状态:** complete
|
||||
- 用户已确认进入代码实施,并指定侧栏底部入口位置。
|
||||
- 已补充看板视觉主张、内容结构和交互原则;本轮仍未改产品代码。
|
||||
- 已把“先记录现有行为基线、功能开关默认关闭”设为实施第一道门禁。
|
||||
- 已创建 Trellis 任务 `08-03-usage-statistics-dashboard`。
|
||||
- 已记录现有高级会话检索相关脏文件,后续不得覆盖或清理。
|
||||
- 计划审查指出缺少独立的全部主题适配阶段;已补充 Phase 5 和对应硬性验收门槛。
|
||||
- 已确认 codebase-memory 索引可用,并开始定位会话写入、MCP toolCalls、composerMentions 和前端入口链路。
|
||||
- 第二次计划审查已通过;已修正 `created` 字段口径并加入 schema 版本和性能阈值。
|
||||
- 已完成后端数据结构、前端入口、回归落点和 11 个主题的并行只读审计。
|
||||
- 已确定看板使用 `.chat-main` 局部覆盖、独立状态机、独立请求 ID 和懒加载派生索引。
|
||||
- 实施清单第 1 项完成,进入只读统计聚合模块与单元测试实现。
|
||||
- 新增 `lib/usage-statistics.js` 和 `scripts/usage-statistics-unit.js`;语法检查、纯夹具单测和 `git diff --check` 通过。
|
||||
- 实施清单第 2 项完成,进入独立 WebSocket 统计查询协议接入。
|
||||
- 独立协议、指定入口、看板 DOM、前端状态机、趋势/明细交互及基础响应式样式已完成首轮实现。
|
||||
- 已通过 `node --check`、统计单测和相关文件 `git diff --check`,进入全部现有主题适配。
|
||||
- 已完成 CoolVibe、Carbon/Nocturne/Cinder、Gilded、Wasteland 的限定覆盖;其他主题直接继承看板语义变量。
|
||||
- 已新增 `usage-statistics` 回归目标,真实 WebSocket 验证查询不会额外发送 `session_list`,禁用开关返回 `disabled`。
|
||||
- 83.3 MB / 134 个真实会话最终首次回填 1.15 秒,查询 P95 51.79 ms,峰值 RSS 116.7 MB,索引占源数据 0.47%。
|
||||
- Chrome Headless 151 完成 11 个主题 × 5 个视口共 55 组布局验收;桌面、平板、移动端均无页面级横向溢出。
|
||||
- 已验证 loading、MCP 长工具名明细、空态和无效日期错误态;Wasteland 根看板改为实色,避免下层输入框视觉透出。
|
||||
- `gilded-theme`、`wasteland-theme`、`advanced-session-search`、统计专项和全量回归均通过。
|
||||
|
||||
### Phase 7:最终审查与交付
|
||||
|
||||
- **状态:** complete
|
||||
- 两轮独立审查均无高等级问题;提出的 4 个中等级问题已全部修复并补回归合同。
|
||||
- 已限制 MCP/Skill 返回规模、公开 total/returned、明确明细“最近 X / 共 Y 条”,回填改为每文件让出事件循环。
|
||||
- 已修复异常空文件增量可能保留旧文档,并把聚合核心单测接入统计专项和全量回归。
|
||||
- 已修复 CoolVibe 选中按钮对比度和暗色日期原生图标;Chrome computed style 验证通过。
|
||||
- PM2 服务已在线加载,用户确认最终效果可用。
|
||||
- 最终清理只删除本任务生成的临时浏览器包、截图和根目录 TODO CSV;未清理既有高级检索改动。
|
||||
|
||||
## 本轮文件变更
|
||||
|
||||
- `lib/usage-statistics.js`:新增懒加载、可重建的只读统计索引与聚合器。
|
||||
- `scripts/usage-statistics-unit.js`:新增统计口径、隐私、增量和损坏文件单测。
|
||||
- `server.js`:新增功能开关、安全生命周期钩子和独立查询协议。
|
||||
- `public/index.html`:新增固定 footer 入口和 chat-main 局部看板 DOM。
|
||||
- `public/app.js`:新增独立看板状态、查询、渲染和恢复交互。
|
||||
- `public/style.css`:新增基础响应式看板与现有主题限定覆盖。
|
||||
- `scripts/regression.js`:新增统计专项合同和真实 WebSocket 回归。
|
||||
- 计划、Trellis 和临时 CSV 仅用于过程记录;现有高级检索脏改动全部保留。
|
||||
|
||||
## 验证结果
|
||||
|
||||
| 验证项 | 预期 | 实际 | 状态 |
|
||||
|---|---|---|---|
|
||||
| 单元与专项 | 统计口径、隐私、协议隔离 | 全部通过 | 通过 |
|
||||
| 全量回归 | 现有功能不回归 | `Regression checks passed.` | 通过 |
|
||||
| 性能 | 回填 ≤3s、P95 ≤100ms、RSS ≤160MB、索引 ≤35% | 1.15s / 51.79ms / 116.7MB / 0.47% | 通过 |
|
||||
| 浏览器 | 11 主题 × 5 视口、动态状态 | 55 组 + loading/detail/empty/error | 通过 |
|
||||
| 风险隔离 | 独立入口、协议、索引和关闭开关 | `CC_WEB_USAGE_STATISTICS=0` 可即时关闭 | 通过 |
|
||||
|
||||
## 错误日志
|
||||
|
||||
| 时间 | 错误 | 尝试 | 处理 |
|
||||
|---|---|---:|---|
|
||||
| 2026-08-03 | 无 | 1 | — |
|
||||
| 2026-08-03 | `task.py list-context` 不接受 action 位置参数 | 1 | 已有 `task.py validate` 通过结果,改为由实现代理读取 `implement.jsonl` |
|
||||
| 2026-08-03 | Wasteland 回归把三暗色统计 selector 识别为五主题共享层 | 1 | 将统计暗色组改用等价 `:where(...)`,保留既有共享层合同 |
|
||||
| 2026-08-03 | 浏览器验收把局部表格 scrollWidth 误报为页面溢出 | 1 | 以 document/body/dashboard body 为页面口径,表格保留明确的局部横向滚动 |
|
||||
| 2026-08-03 | 动态状态脚本刚完成查询即刷新,命中 250ms 限流 | 1 | 按真实操作节奏增加 350ms 间隔,协议行为符合设计 |
|
||||
|
||||
## 5 问检查
|
||||
|
||||
| 问题 | 答案 |
|
||||
|---|---|
|
||||
| 当前在哪? | 功能与验收完成,正在最终独立审查和清理 |
|
||||
| 接下来去哪? | 审查结论、运行服务激活、交付记录 |
|
||||
| 目标是什么? | 不影响现有功能地增加只读使用统计看板 |
|
||||
| 已学到什么? | 见 `findings.md` |
|
||||
| 已完成什么? | 整体功能、全主题适配、专项/全量回归、性能和浏览器验收 |
|
||||
151
.planning/usage-statistics-dashboard-plan/task_plan.md
Normal file
151
.planning/usage-statistics-dashboard-plan/task_plan.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# 任务计划:cc-web 使用统计看板
|
||||
|
||||
## 目标
|
||||
|
||||
在不改变现有聊天、会话列表、会话检索和消息发送行为的前提下,为 cc-web 增加一个可独立启停、只读、可回滚的使用统计看板。
|
||||
|
||||
## 当前阶段
|
||||
|
||||
已完成:功能、主题、回归、性能、浏览器与在线验收
|
||||
|
||||
## 实施阶段
|
||||
|
||||
### Phase 1:边界与统计口径确认
|
||||
|
||||
- [x] 明确当前可可靠统计的数据
|
||||
- [x] 明确当前不可可靠统计的数据
|
||||
- [x] 定义“不影响现有功能”的不可变边界
|
||||
- **状态:** complete
|
||||
|
||||
### Phase 2:安全基线、只读统计核心与历史回填
|
||||
|
||||
- [x] 记录现有回归结果及消息发送、会话切换、检索的行为基线
|
||||
- [x] 建立独立功能开关,`CC_WEB_USAGE_STATISTICS=0` 时不显示入口、不创建索引
|
||||
- [x] 新增独立统计模块,读取保留期内的 `sessions/*.json`
|
||||
- [x] 第一次打开看板时懒加载回填,不把 83.3 MB 扫描放进服务启动或消息处理链路
|
||||
- [x] 将聚合结果写入独立索引文件,不回写任何会话 JSON
|
||||
- [x] 索引就绪后通过防抖 save/delete 旁路钩子更新,钩子失败不进入主错误链路
|
||||
- [x] 索引只保存统计事件、源文件指纹和会话元数据,可删除重建
|
||||
- [x] 索引异常时返回看板错误,不阻塞聊天和会话持久化
|
||||
- **状态:** complete
|
||||
|
||||
### Phase 3:独立查询协议
|
||||
|
||||
- [x] 新增只读 `usage_stats_query/result/error` 协议
|
||||
- [x] 响应携带稳定的 `schemaVersion=1`
|
||||
- [x] 请求参数仅包含时间范围、时区和内部固定上限
|
||||
- [x] 响应包含概览、趋势、MCP 明细、Skill 显式使用排行和会话明细
|
||||
- [x] 不修改 `session_list`、`search_sessions`、`load_session` 等现有协议和负载结构
|
||||
- [x] 增加排行/明细上限、频率限制和异常隔离
|
||||
- **状态:** complete
|
||||
|
||||
### Phase 4:独立看板工作区
|
||||
|
||||
- [x] 在侧栏固定 footer 增加独立“统计”入口
|
||||
- [x] 看板使用自己的前端状态、DOM 根节点和样式命名空间
|
||||
- [x] 顶部提供本周、本月、自定义时间和刷新
|
||||
- [x] 展示可靠指标,不展示无法准确解释的“活跃会话”
|
||||
- [x] MCP 支持明细窗口,会话行可返回对应会话
|
||||
- [x] 关闭看板后恢复原聊天滚动、焦点和输入选区
|
||||
- **状态:** complete
|
||||
|
||||
### Phase 5:全部现有主题适配
|
||||
|
||||
- [x] 枚举 `THEME_OPTIONS` 中全部 11 个现有主题
|
||||
- [x] 适配侧栏底部入口、概览、趋势、表格、空态、错误态和加载态
|
||||
- [x] 主题差异只通过语义变量和主题限定选择器实现,没有复制业务 DOM 或 JS
|
||||
- [x] 11 主题 × 5 视口完成真实浏览器几何验收
|
||||
- [x] 检查 `prefers-reduced-motion`、文字对比度、日期图标和焦点可见性
|
||||
- **状态:** complete
|
||||
|
||||
### Phase 6:兼容性、性能与故障回归
|
||||
|
||||
- [x] 执行现有全量回归并记录结果
|
||||
- [x] 增加统计协议、时间边界、失败状态、空数据、限额和隐私测试
|
||||
- [x] 增加入口隔离、刷新、错误态和 reduced-motion 前端合同
|
||||
- [x] 验证统计查询后会话加载、会话搜索及 `session_list` 均不受影响
|
||||
- [x] 验证索引缓存复用、增量、删除、空文件和损坏文件隔离
|
||||
- [x] 对当前 83.3 MB 会话数据完成性能验收
|
||||
- **状态:** complete
|
||||
|
||||
### Phase 7:灰度启用与回滚
|
||||
|
||||
- [x] 使用功能开关控制统计入口和统计查询服务
|
||||
- [x] 当前环境已重启启用,用户已确认在线效果
|
||||
- [x] 记录 CPU、内存、回填、查询耗时和索引比例
|
||||
- [x] 回滚时可设 `CC_WEB_USAGE_STATISTICS=0`,不涉及会话数据迁移
|
||||
- **状态:** complete
|
||||
|
||||
## 统计口径
|
||||
|
||||
| 指标 | 定义 | 时间归属 | 可靠性 |
|
||||
|---|---|---|---|
|
||||
| 新建会话 | `created` 落在时间范围内的会话数量,兼容 `createdAt` 历史兜底 | 会话创建时间 | 保留会话范围内高 |
|
||||
| 发送消息 | 时间范围内当前仍被保留的用户消息数量 | 用户消息时间 | 保留数据范围内高 |
|
||||
| 自动协作消息 | 非人工触发、由协作链路产生的消息数量 | 对应消息时间 | 先验证字段;无法稳定分类则首版不展示 |
|
||||
| MCP 调用 | assistant message 中 `toolCalls` 的 MCP 调用条目数量 | 暂按所属 assistant 消息时间 | 中 |
|
||||
| MCP 失败 | MCP 调用中 `status=failed` 的条目数量 | 暂按所属 assistant 消息时间 | 中 |
|
||||
| Skill 显式使用 | 用户消息 `composerMentions` 中显式 `$skill` mention 数量 | 用户消息时间 | 保留数据范围内高 |
|
||||
|
||||
## 当前版本明确不做
|
||||
|
||||
- 不统计 Skill 文件被模型实际读取或执行的次数。
|
||||
- 不将“活跃会话”作为指标。
|
||||
- 不修改历史或未来的会话 JSON schema。
|
||||
- 不把统计字段塞入 `session_list` 或会话列表负载。
|
||||
- 不为统计而改造现有消息发送、会话检索或 MCP 执行链路。
|
||||
- 不承诺历史 MCP 的精确调用时刻和耗时。
|
||||
- 不在页面每次刷新时全量扫描所有会话 JSON。
|
||||
|
||||
## 核心架构决策
|
||||
|
||||
| 决策 | 理由 |
|
||||
|---|---|
|
||||
| 采用“历史回填 + 异步增量索引 + 独立查询” | 当前全量扫描可用但长期不适合每次看板请求执行 |
|
||||
| 首版使用可重建的旁路索引,不引入数据库依赖 | 降低发布和 CentOS 单文件打包风险;当前数据规模足够使用 |
|
||||
| 索引与会话源数据完全分离 | 索引可以删除重建,不给现有会话数据带来迁移风险 |
|
||||
| 新增独立 WebSocket 消息类型 | 避免改变现有会话列表、搜索和加载协议 |
|
||||
| 看板作为独立工作区 | 页面结构可复用现有外壳,同时隔离聊天状态和交互 |
|
||||
| 提供可立即关闭的功能开关 | 用户确认落地后默认展示入口;出现问题时可只关闭看板,不回滚会话功能 |
|
||||
| MCP 历史数据暂按 assistant 消息时间聚合 | 当前工具调用记录本身没有时间戳,必须在界面说明口径 |
|
||||
|
||||
## 看板界面基线
|
||||
|
||||
- **视觉主张:** 延续 cc-web 现有应用外壳,形成安静、紧凑、可快速扫描的运营工作区;不单独设计营销主题。
|
||||
- **内容结构:** 时间与刷新控制 → 核心数字 → 趋势和 MCP 明细 → 状态与 Skill 排行 → 最近会话。
|
||||
- **交互主张:** 切换看板时保留聊天现场;筛选刷新只更新看板区域;明细查看在看板内部完成,返回后保留筛选条件。
|
||||
- **组件原则:** 只有可交互或需要独立语义的区域使用卡片,其余优先使用分栏、分隔线、图表和紧凑表格,避免卡片拼贴。
|
||||
- **入口位置:** 固定在侧栏底部操作区,处于会话列表下方,与截图中的 `CC-Web` 设置入口同一区域;不插入会话列表滚动内容。
|
||||
|
||||
## 不影响现有功能的硬性验收门槛
|
||||
|
||||
1. 关闭功能开关时,前端 DOM、交互和服务端行为与改动前一致。
|
||||
2. 统计模块异常、索引缺失或查询超时,消息发送和会话操作仍正常。
|
||||
3. `session_list`、`search_sessions`、`load_session` 的请求和响应合同不变。
|
||||
4. 不修改现有会话文件内容;统计索引可随时删除并从源数据重建。
|
||||
5. 全量现有回归通过,新增统计回归通过后才允许灰度开启。
|
||||
6. 不在正常消息处理的同步关键路径中执行全量扫描或重聚合。
|
||||
7. 看板必须显示“基于当前保留数据”和最后更新时间,不能把保留期数据表述为永久审计数据。
|
||||
8. `THEME_OPTIONS` 中全部现有主题都必须完成桌面和窄屏验收;侧栏入口位置一致,看板内容可读,且聊天恢复、会话切换、搜索、消息发送和 MCP 功能不受影响。
|
||||
|
||||
## 发布门禁建议
|
||||
|
||||
- 基线:先记录当前回归结果和典型聊天操作耗时。
|
||||
- 开发态:仅显式开启功能开关后显示入口。
|
||||
- 灰度态:限制单用户或指定环境开启,观察至少一个完整统计周期。
|
||||
- 正式态:只有错误率、CPU、内存和 P95 查询耗时满足阈值才默认开启。
|
||||
- 回滚:关闭功能开关;保留独立索引无害,必要时可后续清理。
|
||||
|
||||
## 首版性能阈值
|
||||
|
||||
- 当前约 82 MB 会话数据的专项回填目标不超过 3 秒。
|
||||
- 索引就绪后的本周/本月查询 P95 目标不超过 100 ms。
|
||||
- 专项回填进程峰值 RSS 目标不超过 160 MB。
|
||||
- 旁路索引文件目标不超过源会话 JSON 总量的 35%。
|
||||
|
||||
## 错误记录
|
||||
|
||||
| 错误 | 尝试 | 处理 |
|
||||
|---|---:|---|
|
||||
| 无 | 1 | — |
|
||||
| `task.py list-context ... implement` 参数不被当前脚本接受 | 1 | `task.py validate` 已确认 context JSONL 合法;后续直接读取任务上下文文件,不重复该命令 |
|
||||
Reference in New Issue
Block a user