README · 交接索引

README · 小手机(猫爪聊天)交接索引

生成时间:2026-10-27 00:45(北京时间) 生成方式:只读整理。未修改任何代码、未部署、未写入/迁移/删除任何生产数据。 适用对象:接手本项目的新 AI / 开发者。


1. 这份档案是什么

把 2026-10-05 ~ 10-27 的全部可访问历史(对话摘要、任务书、源码、提交、测试、部署、数据面)整理成 10 个文件,让接手者尽量不需要再问用户「以前做过什么」。

它不是:项目宣传稿、完整源码包、或对未来的承诺。它是「现状 + 历史 + 坑 + 待办」的底账。


2. 阅读顺序

顺序 文件 什么时候读
1 README_交接索引.md(本文) 先读,知道资料边界
2 01_项目总览与长期目标.md 建立全貌:是什么、要变成什么
3 04_技术架构与当前代码基线.md 知道代码怎么跑、数据在哪
4 07_问题风险与失败经验.md 动手前必读:哪里会炸、哪些事别重做
5 03_任务清单与真实进度.md 知道做到哪、哪些只是雏形
6 05_长期记忆与收藏系统历史档案.md 要动记忆/删除/检索时必读
7 06_决策记录与版本变化.md 想知道「为什么是这样」时查
8 02_历史需求与设计构想.md 要还原原始需求、找「提过但没做」的东西
9 09_缺失资料与待确认事项.md 准备问用户之前先看,避免重复问
10 08_下一阶段执行路线.md 决定要开工时按它排期

3. 文件清单与作用

文件 内容 关键结论
01_项目总览与长期目标.md 项目是什么、最初需求、12 项长期目标、三端入口、状态快照 三端跑同一份修复版;数据四份副本
02_历史需求与设计构想.md 按主题整理的全部需求(UI/安全/任务书/记忆/托管)+ 禁止事项 + 提过没做的 阶段 5–9 全未开始
03_任务清单与真实进度.md 30+ 任务的状态、证据、缺口 只有 Mock 测试,无生产 E2E
04_技术架构与当前代码基线.md 仓库/文件/构建链/路由/键位/三端配置/数据量/测试/成本 v1 = 历史主库;v2 = 全新空库
05_长期记忆与收藏系统历史档案.md 记忆五代演进、当前实现、设计差距、必须保留的约定 双写 mem:+msg: 是最大冗余
06_决策记录与版本变化.md 20 条决策(区分用户确认/AI 建议)+ 版本时间线 + 冲突 v2 空库是设计,不是故障
07_问题风险与失败经验.md 14 个事故档案(含根因与教训)+ P0–P3 风险 + 别重做的清单 P0 三项:寻址分裂、删不彻底、令牌公开
08_下一阶段执行路线.md 阶段 0/A–F 的依赖排序、验收、回滚、审批 先止血,再修数据,最后做阶段 5–9
09_缺失资料与待确认事项.md 资料三分类、冲突、要用户补的材料、7 个待确认决定 先定「主数据源」与「279 条删不删」

4. 资料来源与核实等级

4.1 实际读取过

4.2 知道存在但未读全

猫爪聊天-完整项目.md(304KB)、handoff-public.md / 小手机-交接-公开版.md(40KB×2)、替换指南.md、给其他AI-代码包/、小手机-审计草案/01–04、17 张截图、小手机交接/index.html。

4.3 无法访问

GitHub 远端实时状态、Cloudflare 线上日志、KV 精确键列表(配额耗尽)、secret 明文、用户浏览器数据、平台控制台、10-22 之前的逐字对话(仅存摘要)、部分已过期的截图链接。

4.4 证据标注


5. 三句话结论

  1. 代码是好的,架构有三处硬伤:前后端两套寻址规则(聊天走设置、记忆走同源)、消息存两份但只删一份、页面公开下发令牌。
  2. 数据是散的:v1 KV(历史主库,含 279 条软删)、v2 KV(全新空库)、国内版 SQLite(307 条导入+13 条新聊)、每台浏览器 IndexedDB(图只在这),四份互不同步。
  3. 长期目标只完成了前四阶段:阶段 5 人设版本、6 成长分析、7 后台、8 沈屿迁移、9 清理,全部未开始。

6. 交接时的最小动作清单

接手者第一小时建议只做这些(全部只读):

cd /workspace/chat
git log --oneline -20
git status
ls -la
wc -l worker.js index.html build.js deploy-web/server.js deploy-web/kv-sqlite.js

然后:

  1. 读 01 → 04 → 07;
  2. 用 Cloudflare API 核对 v1/v2 线上版本(不要 wrangler deploy);
  3. 用只读方式查国内版 GET /health;
  4. 把 09 里的三个问题问用户;
  5. 得到答复后再看 08,不要直接开工。

7. 红线(写在这里,防止新 AI 一上来就踩)

  1. 不改用户没点名的区域;不重构、不改 UI 风格。
  2. 不手工编辑 cat-chat-all-in-one.js;改完 worker.js/index.html 必须 node build.js。
  3. 不部署、不清库、不改密钥,除非用户明确说「可以」。
  4. 不批量删除;任何清理 ≤200 条/批、先导出、跨天做。
  5. 不把「Mock 测试通过」说成「线上可用」。
  6. 不把 AI 的建议写成「用户已同意」。
  7. 不在文档里写任何真实密钥/令牌/密码。

01 · 项目总览与长期目标

01 · 项目总览与长期目标

档案版本:2026-10-27 | 证据等级见 README | 本文不含任何密钥、令牌或密码


1. 一句话

「小手机」= 猫爪聊天:一个单页、仿微信、手机优先的 AI 伴侣聊天 Web App。前端是唯一真源 index.html,后端是唯一真源 worker.js,构建时内联成单文件 cat-chat-all-in-one.js 部署到 Cloudflare Workers;另一套 Node 垫片(deploy-web/)把同一个 worker.js 跑在国内托管平台上,用 SQLite 代替 KV。数据按「角色(profile)」隔离,记忆分「长期记忆碎片 / 结构化档案 / 高光原话 / 经历 / 关联关系」五类,模型 Key 由用户在前端自填。

2. 项目起因与使用场景

3. 最初的需求(2026-10-05,原文要点)

用户第一条任务书写明「绝对不准重构我的代码结构,必须保留现有布局」,并要求一次性完成五组功能:

组 要求
一、前端 UI ①果冻气泡(backdrop-filter: blur(8px)、半透明白底、微高光边缘、圆角 16px、最大宽 75%)②圆形头像、必须可自定义上传,不写死默认图 ③猫爪印面板:点开先弹「颜文字 / 表情包」二级选项,颜文字点击填入输入框,表情包支持本地上传转 Base64 与粘贴 URL,右侧麦克风占位 ④长按菜单:复制 / 引用回复 / 删除单条 / 收藏,引用时输入框上方显示原文 ⑤消息状态:发送中 / 成功 / 失败可重试 ⑥动态状态胶囊:按后端 ai_status 渲染,没有就不显示(不留空白框)
二、数据与请求 ①localStorage 换 IndexedDB ②删掉 setTimeout 假回复,改真实 fetch,报错显示在气泡里
三、后端与记忆(核心) ①外接 Cloudflare D1 或 KV,用户输入与 AI 回复原封不动入库并带时间戳 ②每次发消息检索最相关历史原话 + 人设打包给模型,预留 Worker 地址 ③请求体预留 tools: []
四、高级功能 ①时间感知:前端带系统时间,后端注入提示词 ②主动消息:按时间间隔触发,每天 2–3 次
五、绝对禁止 ①不准一次性给几百行代码,要拆成几步教用户在手机上替换 ②不准重构结构、不准改 UI 风格

当时通过提问确认的三个方案(用户已确认): 1. 后端 Worker 统一转发(Key 不进前端直连); 2. 检索先用关键词匹配(不上向量库); 3. 主动消息由页面打开时检查(不用 Cron)。

4. 长期目标(任务书 v1.0,12 项能力)

来源:/root/uploads/1792388616892580314-文本.txt(《小手机项目|完整系统升级与安全改造任务书 v1.0》,27KB)

  1. 完整保存所有聊天原文
  2. 可靠的本地与云端同步机制
  3. 独立的高光原话库
  4. 结构化的经历记忆
  5. 可追溯来源的记忆时间线与关联关系
  6. 可搜索、可导出、可恢复的完整聊天档案
  7. 分层人设系统与人设版本管理
  8. 定期批量运行的人设成长分析
  9. 带证据、反例、审核、回滚的成长建议流程
  10. 统一的后台管理系统
  11. 沈屿本体的历史聊天 / 人设 / 记忆迁移
  12. 控制 Token 与 Cloudflare 成本

核心原则(原文):保留现有可用能力,优先修复真问题,再逐步加功能;禁止为了新功能破坏原有项目。

5. 当前三个线上入口

入口 地址 运行时 存储 定位
v1 https://zhai.beibbloveme.workers.dev Cloudflare Worker zhai KV chat_memory_v2 历史主库:真实聊天归档与记忆都在这里(用户长期使用的入口)
v2 https://zhai-v2.beibbloveme.workers.dev Cloudflare Worker zhai-v2 KV chat_memory_v3 按用户要求全新重建的空环境(2026-10-24 深夜),不是搬迁失败
国内版 https://a0cd1755f00a22a37.app.workbuddy.host Node 22 + server.js 垫片 /tmp/cat-chat-data/chat.db(SQLite) 国内直连可用、无 KV 配额限制;数据是 v1+v2 合并导入 + 新聊

命名坑(务必记住):chat_memory_v2 是 v1 的库;chat_memory_v3 才是 v2(zhai-v2)的库。用户口中的「v3 后端 = 全新的那个 = 配置里叫 v2」,指的是 zhai-v2 + chat_memory_v3。

6. 当前状态快照(2026-10-27 00:45)

维度 状态
代码 分支 main,HEAD 5666a72(2026-10-25 20:36),工作区无已修改文件(仅有未跟踪的文档/截图)
v1 已部署 a3ef2a33(10-25 22:54),含「KV 配额烧穿」修复;线上 bundle 与本地逐字节一致【线上核验】
v2 已部署 8c6fe98a(10-25 20:38),同一份代码(etag 与 v1 相同)【线上核验】
国内版 进程 22:26 重启,已加载新代码;页面注入 cat-web-token 与 __CAT_WEB_MODE__【实测】
聊天可用性 国内版完全可用;v1/v2 在 10-26 08:00(北京时间)KV 配额重置前处于降级模式(能聊、提示「云端检索今日配额已用完」)
数据规模 国内版 SQLite:652 键(消息 320 / 记忆 312 / 高光 2 / 经历 1 / 元数据 2 / 限流 14 / 主动计数 1 / 墓碑 0)【实测】
测试 tests/ 共 10 个脚本、约 206 项断言,全部为「内存/SQLite Mock + Mock 模型」的契约测试,无生产 E2E【源码】
最大风险 见 07:P0 三项(前后端寻址分裂、彻底删除不清 mem:、页面公开下发令牌)

7. 与原设计的差距(一句话版)

详细依据见 03_任务清单与真实进度.md、05_长期记忆与收藏系统历史档案.md。


02 · 历史需求与设计构想

02 · 历史需求与设计构想

按「主题 + 时间」整理。每条尽量保留原始措辞的关键含义。 标注:✅ 已实现 | 🟡 部分实现/雏形 | ⬜ 已提出未实施 | ❌ 已撤销/被取代 证据:对话摘要(30 个历史阶段摘要)、任务书原文、Git 提交、当前代码。


一、初始需求(2026-10-05)

见 01_项目总览与长期目标.md 第 3 节(五组要求逐条列出,不再重复)。三条关键约束至今有效:

  1. 不准重构代码结构、不准改 UI 风格;
  2. 不准一次性甩几百行代码,要拆成可复制粘贴的步骤(手机操作);
  3. 用户输入与 AI 回复必须原封不动入库。

已确认的技术选型:Worker 统一转发 / 关键词检索 / 页面打开时检查主动消息。


二、UI 与交互:历次明确要求

2.1 第一批(10-05 ~ 10-06,✅)

要求 结果
果冻/液态玻璃气泡(blur 8px、半透明白、微高光、圆角 16、宽 75%) ✅
头像可自定义上传,不写死任何默认图 ✅(内置无头像时用文字占位,不写死图片)
猫爪面板:颜文字 / 表情包(本地上传转 Base64 + 粘贴 URL)/ 右侧麦克风占位 ✅
长按菜单:复制 / 引用 / 删除单条 / 收藏;引用时上方显示原文 ✅
消息状态:发送中 / 成功 / 失败可重试 ✅(后续加了秒级时间,再后又改回居中时间条)
动态状态胶囊:按 ai_status 渲染,无则不显示空白框 ✅
IndexedDB 替代 localStorage 存聊天记录 ✅
真实 fetch 替代假回复,错误显示在气泡 ✅

2.2 外观细化(10-14 ~ 10-16,✅ / 一处回滚)

日期 要求 状态
10-14 手绘气泡框(猫/云/星星/蝴蝶结装饰,参照截图) ❌ 上线后中文乱码(atob 字节问题)→ 回滚
10-14 液态玻璃 + 「我的气泡粉白少女心」 ✅
10-14 阴影范围缩小、颜色加深 ✅
10-14 状态文字:头像正上方、白字、明显深阴影 ✅(后修正居中、过长贴边不裁切)
10-14 表情包点击直接发送 + 发送前自动压缩 ✅
10-14 文案「自定义标题 → 备注喵」「自定义状态 → 状态喵」 ✅
10-14 设置分「头像与聊天背景」「后端与模型」两组折叠 ✅
10-14 猫爪面板重排 + 照片三入口(上传 / 拍照 / 文件) ✅
10-14 新增收藏(微信式 UI、双向收藏、显示收藏者) ✅
10-14 发送状态移到气泡下方 + 成功后秒级时间;行距调大 ✅(10-17 改为居中时间条)
10-16 顶部全屏按钮(Fullscreen API,图标随状态切换) ✅
10-16 PWA:添加到主屏幕无地址栏启动(manifest + 爪印图标 + apple meta) ✅
10-16 图片全屏灯箱(点击查看、点击关闭) ✅
10-16 showToast 轻提示组件 ✅

2.3 10-17:功能与观感修正(✅)

  1. 修发送键(点击事件被当成文本,出现 [object PointerEvent]);
  2. 修头像阴影被重复定义覆盖;
  3. 新状态只跟新消息,不全量改写历史状态;
  4. 取消气泡下方秒级时间 → 微信式居中时间条 2026.10.15 10:20;
  5. 顶部栏 / 底部输入栏改悬浮圆角玻璃卡片;
  6. AI 认知纠正:知道自己是 AI;「状态」= 心情/情绪,不是任务或地点;
  7. 建立《模块索引.md》,以后按区域点单改代码。

2.4 10-17 第二轮(✅)

照片三入口面板 / iOS 高透液态玻璃 / 自定义气泡皮肤(PNG 九宫格 border-image-slice:33%,分「我的/对方」)/ 顶栏、底栏、全局底色可单独调 / 气泡颜色+透明度+磨砂分双方 / 表情包按微信规范压缩(长边 ≤240,透明图存 PNG,显示 ≤150px)/ 状态文字居中贴边 / 一键恢复默认外观(含深色模式)。

2.5 10-22:iOS 全屏与键盘(✅,过程极曲折)

用户原话演进(保留原意):

最终方案(✅,提交 f02cc07 / 线上 51ac31d3):

2.6 10-24:输入框与删除(✅,9ed08a6 / 线上 0114d69f)

2.7 10-25:删除语义(✅)


三、安全改造(10-17 第三轮,✅)

用户 7 条要求 → 实现与验证:

# 要求 实现
1 接口身份验证(防未授权/绕过) X-Cat-Token + 同源校验;所有 POST、/proactive、/backup、/init 必须带令牌
2 API Key 保护 出站白名单、https+443、禁内网 IP、禁重定向、错误脱敏
3 频率/体积/模型额度 IP 分钟 20、IP 日 400、全局 1500;body 1MB(导入 8MB);消息 4000 字;图片 800KB;max_tokens 500
4 KV/记忆越权 全部接口置于鉴权之后;后续再加 profile 隔离
5 提示词注入/输入校验 <<< >>> 包裹数据 + 最高优先级安全段 + stripMarkers + 控制字符清洗
6 SSRF/开放代理/重定向 白名单 + 禁 IP 字面量 + redirect:'manual' + 3xx 拒绝
7 日志/轮换/备份/回滚 rid 追踪、wrangler secret put AUTH_TOKEN 轮换、/backup 导出、wrangler rollback

测试:后端 46 项断言 + Playwright 前端回归(当时全过)。

遗留:令牌随页面下发(知道地址即可拿令牌),当时已如实记录为「未解决风险」,任务书阶段 7 才计划做独立强认证。


四、《完整系统升级与安全改造任务书 v1.0》阶段 0–9

原文:/root/uploads/1792388616892580314-文本.txt

阶段 目标 状态 说明
0 最新源码审计(只读,输出 A–E 五份清单) ✅ 审计报告 + 修复清单,10-19 完成
1 备份与基础可靠性(只修确认存在的问题) ✅ 修 B1 误清空、备份恢复、保存失败反馈、幂等
2 统一数据结构(角色/会话/消息/来源/同步状态) ✅ profile_id / message_id / conversation_id / sync_status、msg:<profile>:<mid>
3 完整档案与搜索(查询/追溯/导出/恢复/重试) ✅ /archive(含 keyword/from/to/speaker/分页)、/export/full、/import/full
4 高光与经历记忆(时间线/关联/来源/不确定标注) ✅ hl/exp/rel + 记忆库面板 + 候选经历单列
5 人设版本管理(独立保存、版本、对比、回滚) ⬜ 未开始 当前人设只是一段 localStorage 文本
6 批量成长分析(任务队列、证据/反例、待审核) ⬜ 未开始 无任何成长分析代码
7 后台管理(档案/记忆/人设/任务/备份) 🟡 雏形 只有设置面板里的记忆库三标签;无独立后台、无强认证
8 沈屿本体迁移(历史/人设/记忆) ⬜ 未开始 当前是测试角色 owner
9 测试环境清理 ⬜ 未开始 测试数据仍在(v1 归档 279 条软删、国内版 307 条导入)

任务书还规定:每阶段必须交付 11 项内容(目标/文件清单/改动说明/API 变更/迁移风险/测试结果/未解决问题/回归/部署验证/回滚/下阶段建议),未实际运行的测试不得声称通过。


五、记忆系统构想(详见 05)

按代际:

代 构想 状态
v0 所有消息原封不动存 KV(mem_<ts>_<rand>),关键词检索最近片段 ✅ 已实现(旧键保留只读)
v1 按 profile 隔离(mem:<profile>:*),修跨角色泄漏 ✅
v2 结构化档案(msg:<profile>:<mid>)+ 稳定 mid + 幂等归档 ✅
v3 高光原话(必须引用真实消息)+ 经历(active/candidate + source_mids + 确定性)+ 关系(5 种类型) ✅
v4 参考 Mem0/Graphiti/Letta/LangGraph 的四态记忆、双时间戳、版本链、token 预算 ⬜ 仅调研 + 5 条建议,未实施

六、托管与部署构想

时间 要求 状态
10-24 「重新建一个后端……不是一个网址」「全部重新部署吧一个全新的」 ✅ 新建 zhai-v2 + chat_memory_v3(空库),旧环境保留
10-24 「暂时先部署到 WorkBuddy 上面,后端那个记录的先不用」 ✅ 国内直连测试版(先无 KV,后加 SQLite)
10-25 「理论上来说你是不是可以把后端相同的迁移到那个阿里云的云服务器?」→ 给四方案 → 用户选 C(先验证 WorkBuddy 存储持久性) ✅ 四批完成(SqliteKV / 垫片 / 回归 / 双快照 / 搬迁)
10-25 「情况一(v2 搬迁)情况二(导出导入恢复)都考虑,做一个版本」 ✅ 双快照三层防护 + 手动导出导入
10-25 「你直接做完吧,不用问我了」 ✅ 第 3、4 批自主推进

国内版的硬约束(平台规则,实测): - 同源请求必须带 ?sharecode=…,否则网关 403「链接不完整」; - 部署目录会被本地代码同步覆盖 → 数据必须放 /tmp; - 反向代理改写 Host → 同源基准只能用固定的可信公网地址。


七、明确「不要做什么」(全部仍然有效)

摘自初始需求、任务书与历次纠正:

  1. 不准重构代码结构、不准改 UI 风格;只改被点名的区域(《模块索引.md》就是为了这个)。
  2. 不准一次性给几百行;要拆成可复制粘贴的步骤。
  3. 不准写死默认头像/默认人设;人设只用用户自定义的。
  4. 不准用假回复(setTimeout);必须是真实请求,错误要显示在气泡里。
  5. 不准把界面已显示消息当成云端保存成功(任务书原话)。
  6. 不准在没备份的情况下覆盖唯一可用版本;不准直接清空 KV/IndexedDB。
  7. 不准把推测当确定事实;不确定的记忆要标 candidate/unknown。
  8. 不准把管理密钥/模型 Key 写进公开 HTML。
  9. 不准只靠隐藏按钮/前端路由保护管理功能。
  10. 不准跳过阶段验收;未实际运行的测试不得声称通过。
  11. 不准未经批准扩大修改范围;发现问题先停下说明风险。
  12. 不准删测试后端/测试数据(阶段 9 之前);删除必须指定 profile 与清单,不能用模糊条件。

八、已提出但未实施(保留,不要丢)

构想 出处 状态
向量检索(Vectorize + embedding) 初始方案选项 ⬜ 用户选了关键词检索;任务书也说「不得视为第一阶段必要」
图片存 R2(云端保留原图) 交付文档「已知限制 2」 ⬜ 涉及付费,未授权
全量导出/导入的前端 UI 交付文档「待办」 🟡 API 已有,前端仅国内版可用
档案搜索面板 + 跳转原消息 交付文档「待办」 ⬜ 后端 /archive?keyword= 已有
人设四层 + 版本管理(阶段 5) 任务书 ⬜
成长队列 + 证据/反例/审核(阶段 6) 任务书 ⬜
独立后台 + 强认证(阶段 7) 任务书 ⬜
沈屿本体迁移(阶段 8) 任务书 ⬜
测试环境清理(阶段 9) 任务书 ⬜
流式回复(SSE) 10-23 基线审查标注【未实现】 ⬜ 未提出明确需求
消息全文搜索 UI 10-23 基线审查标注【未实现】 ⬜
多角色切换 UI 10-23 基线审查标注【未实现】 🟡 后端 profile 已支持,前端固定 owner
深色模式 10-17 用户要求删除 ❌ 已移除入口(CSS 仍有变量残留)
手绘气泡装饰 10-14 ❌ 因乱码回滚,未再尝试

九、需求冲突与待核实

冲突 处理
「云端删除」早期=软删(记录仍在)vs 10-25 用户要求「真的删」 10-25 起改为硬删+墓碑;历史 279 条软删记录仍物理存在(见 07),需一次性清理
「备份要包含记忆」vs 早期导出只含 messages/favorites/settings 已补 /export/full(含 mem/hl/exp/rel);但 v1/v2 的前端导出仍走本机数据(见 07 P1-4)
「v2 空=搬迁失败」(我的错误结论)vs 用户「v2 是全新部署」 以用户为准:v2 空是设计;606 条搬进的是国内版(见 06 D-08)

03 · 任务清单与真实进度

03 · 任务清单与真实进度

状态定义:✅ 已完成(有代码+证据)|🟡 部分完成(链路未闭环)|⬜ 未开始|⏸ 待验证|❌ 已取消/被取代 证据等级:【代码】 源码可见 |【测试】 本地测试跑过 |【部署】 线上版本已确认 |【实测】 我亲自执行并得到结果 |【未验证】 无证据 重要:tests/ 里全部是「Mock KV / Mock 模型」的契约测试,能证明逻辑分支,不能证明生产端到端可用。凡是只有【测试】没有【实测/部署】的,都不算「线上已验证」。


一、任务总表(按主题)

A. 项目奠基与前端 UI

# 任务 状态 证据 未完成/说明
A1 基于用户源码(v6.html)产出改造版 index.html(7 个片段) ✅ 提交 afa0aa4 基线;文件在 /workspace/chat/index.html【代码】 —
A2 果冻/液态玻璃气泡、头像自定义、猫爪面板、长按菜单、消息状态、动态状态胶囊 ✅ 同上【代码】 手绘装饰版曾上线后乱码 → 回滚
A3 IndexedDB 替换 localStorage + 真实 fetch ✅ readMessages/saveMessages/idbSet【代码】 —
A4 收藏(本地 fav_list + AI [[fav:]] 回传) ✅ parseFavorites、addFavorite【代码】 收藏历史只在本机(后加云端高光)
A5 全屏按钮 / PWA / 图片灯箱 / showToast ✅ 提交 08815a9 等【代码】 国内版 PWA 图标受 sharecode 影响(见 07 P2-10)
A6 外观定制(气泡颜色/透明度/磨砂、自定义皮肤九宫格、顶底栏调色、恢复默认) ✅ applyAppearance、applySkinClass【代码】 深色模式入口已按用户要求删除
A7 iOS 全屏与键盘适配(6 轮返工) ✅ 提交 43ec8d7…f02cc07;线上 51ac31d3【部署】 过程见 07 事件 03
A8 输入框 16px 防聚焦缩放 + --bar-h 动态高度 ✅ 提交 9ed08a6;线上 0114d69f【部署】 —
A9 「仅本机删除」与「彻底删除(含云端)」两条路径拆分 ✅ 提交 d7ada29、6a09007;test_ui_delete_scope.mjs【测试】 历史消息无 mid 时只能本地删(旧数据限制)

B. 后端与安全

# 任务 状态 证据 未完成/说明
B1 Worker 后端:KV 记忆、关键词检索、时间注入、主动消息、tools 预留 ✅ worker.js【代码】 主动消息最多 3 次/天、间隔 ≥3h
B2 安全 v2:令牌/同源/限流/SSRF/体积/脱敏/提示词边界 ✅ 提交链 + 46 项断言【测试】 令牌随页面下发未解决(P0-3)
B3 多服务商代理(OpenAI/DeepSeek/通义/自定义) ✅ callModel + validateApiUrl【代码】 v1/v2 ALLOW_CUSTOM_API=0 → 自定义域名会被 403(P2-4)
B4 页面令牌按部署注入(修 v1 401) ✅ 提交 32aa8a5;v1 a3ef2a33 线上拉取验证【线上核验】 v1 无 AUTH_TOKEN → 用代码默认值(弱)
B5 模型 Key 服务端兜底 ❌ v1 secret 叫 DEEPSEEK_API_KEY,代码只读 env.API_KEY【线上核验】 死配置;目前全靠前端自填 Key

C. 任务书阶段 0–4

# 任务 状态 证据 未完成/说明
C1 阶段 0 只读审计 ✅ 审计报告-阶段0.md、小手机-审计草案/【代码/文档】 —
C2 阶段 1 备份与基础可靠性(含 B1 误清空修复) ✅ 提交 08815a9【代码】 —
C3 阶段 2 统一标识(profile / mid / conversation / sync_status) ✅ 提交 e6c9661【代码】 —
C4 阶段 3 完整档案与同步(云端 msg: + 同步/恢复按钮) ✅ 提交 e6c9661【代码】 同步为全量上传,O(n) 写(P1-7)
C5 阶段 4 高光 / 经历 / 关系 + 记忆库面板 ✅ 提交 ca44bb8【代码】 关系不进模型上下文(设计如此)
C6 P0-1 记忆按 profile 隔离 ✅ 提交 9505597;test_p0_memory_isolation 15/15【测试】 旧 mem_* 仅 owner 只读
C7 P0-2 稳定 mid 与引用完整性 ✅ 提交 513f24b;21/21【测试】 —
C8 P0-3 删除接口与防复活 🟡 提交 d4aa34e(软删)→ 83bc573(硬删+墓碑);36/36【测试】 硬删不清 mem:(P0-2)
C9 P0-4 全量导出/导入 ✅ 提交 2460f9a;27/27【测试】 前端 UI 只在「国内版」放开(P1-4)
C10 P1 记忆闭环 + 档案搜索 ✅ 提交 5bfe8c8;13/13 + 14/14【测试】 搜索无前端面板

D. 托管、迁移与部署

# 任务 状态 证据 未完成/说明
D1 Git 仓库接入(GitHub 私有) ✅ github.com/beibbloveme-crypto/yuai;git remote -v【代码】 远端实时状态【未验证】(沙箱 403)
D2 Pages 静态托管适配 🟡 提交 1ad73ae;Pages 项目 chat-ui-beib 实为 10-03 的另一原型【实测】 当前三端都不用 Pages
D3 全新后端重建(zhai-v2 + chat_memory_v3) ✅ 提交 867faee;小手机-全新后端环境说明.md【文档】 空库是设计,不是失败
D4 国内直连测试版(WorkBuddy) ✅ 提交 0b01ef6;线上可访问【实测】 初版无落库,后改 SQLite
D5 C 方案第 1 批:server.js 垫片化 + SqliteKV ✅ 提交 4a8bdb1、e20c937【代码】 —
D6 C 方案第 2 批:测试套件换真 SQLite ✅ 提交 ef6f725;188 项断言【测试】 仍是契约测试,非 E2E
D7 C 方案第 3 批:双快照三层防护 + 导出补全 ✅ 提交 a32fe2e;13/13【测试】 快照与库同在 /tmp(P1-3)
D8 C 方案第 4 批:v2 → 国内版全量搬迁 606 条 ✅ 提交 5f61f81;国内版 counts 与逐条导入报告【实测】 实际构成:307 消息 + 299 记忆;其中 217 条 mid 为 local_(来自本机历史同步)
D9 国内版三连修复(WORKER_URL 污染 / sharecode 403 / 人设丢失) ✅ 提交 c007087、be64807、bd6a5af【代码】 —
D10 国内版 403 根治(可信公网基准) ✅ 提交 0f1007c【代码】 域名一变仍会 403(P1-3)
D11 三端部署同一份修复版 ✅ v1 a3ef2a33、v2 8c6fe98a、国内版进程 22:26【线上核验/实测】 v1/v2 受 KV 配额限制,10-26 08:00 前降级

E. 删除语义与配额

# 任务 状态 证据 未完成/说明
E1 「彻底删除」改物理删除 + 墓碑(v2 先行) ✅ 提交 83bc573;v2 65bf6ef0【部署】 历史 279 条软删未清理
E2 墓碑防复活(导入四类记录前查墓碑) ✅ test_p0_delete_sync 第 7/8 节【测试】 墓碑不参与导出 → 换库恢复会丢墓碑
E3 修复 KV 每日 list 配额烧穿(缓存+降级+干净 503) ✅ 提交 5666a72;test_p0_kv_quota 17/17【测试】;v1/v2 部署【部署】 写配额仍紧(P2-3);检索 1000 键截断(P1-2)

F. 记忆系统升级(任务书 5–9)

# 任务 状态 证据 说明
F1 阶段 5 人设版本管理 ⬜ 无代码 当前人设 = localStorage 一段文本
F2 阶段 6 批量成长分析 ⬜ 无代码 无任务队列、无证据/反例、无审核
F3 阶段 7 后台管理 + 强认证 ⬜ 无代码(只有设置面板) 任务书要求「不得只靠隐藏按钮保护」
F4 阶段 8 沈屿本体迁移 ⬜ 无代码 当前测试角色 owner
F5 阶段 9 测试环境清理 ⬜ 无代码 测试数据仍在(见 04 数据面)
F6 记忆框架升级(Mem0 四态 / Graphiti 双时间戳 / Letta 预算) ⬜ /workspace/小手机项目-全项目代码基线审查报告.md 第七部分(调研+5 条建议)【文档】 只出建议,未实施

G. 文档与交接

# 任务 状态 证据 说明
G1 完整交付文档(源码/功能/进程/测试/交接) ✅ 小手机-完整交付文档.md(10-22,605 行)【文档】 已过期(不含 10-23 之后)
G2 模块索引(按区域改代码) ✅ 模块索引.md【文档】 行号已漂移,选择器名仍有效
G3 安全说明 / 审计报告 / 修复记录 / 迁移方案 ✅ 4 份 md【文档】 均截至各自日期
G4 交接档案(本次 10 个文件) ✅ 本目录【文档】 截至 2026-10-27
G5 交接文档更新到当前基线 🟡 本档案即更新版 旧文档未删改(保留历史)

二、用户明确下达但尚未完成的事

# 事 现状 建议
1 历史 279 条软删记录「真的删掉」 记录仍物理存在(v1 KV + 国内版 SQLite),仅带 deleted_at 分批硬删+墓碑(≤200 条/批,跨天,注意写配额)
2 v1 与国内版数据统一 两份独立副本,互不同步 先定「哪个是主」,再做单向同步
3 图片备份 图只在本机 IndexedDB 决策:接受 / 上 R2 / 只备份缩略图
4 沈屿本体迁移(阶段 8) 未开始 先补人设版本(阶段 5),否则迁移无载体
5 后台管理(阶段 7) 未开始 与 P0-3 访问控制一起做最省事

三、被取消 / 被取代的任务

任务 原因
手绘气泡装饰 上线后乱码,回滚
深色模式 用户 10-17 要求删除入口
默认人设「小喵」 用户 10-19 要求取消,只用自定义人设
deploy.sh 手工改 cat-chat-all-in-one.js 后端 被 build.js 整体重生成取代
软的「云端删除」 被硬删+墓碑取代(10-25)
单一 Worker 架构 被 v1 / v2 / 国内版三端取代
阿里云 ECS 方案 用户选 C(WorkBuddy + SQLite)

四、如何自行复核本表

cd /workspace/chat
git log --oneline --all          # 全部提交
git status                       # 工作区状态
node build.js                    # 重建产物(会重写 cat-chat-all-in-one.js)
for f in tests/*.mjs; do node "$f"; done   # 契约测试

线上版本核对(只读): - Cloudflare API:/accounts/{acc}/workers/scripts/{name} 拉取线上 bundle; - 国内版:curl http://127.0.0.1:3000/health(沙箱内)。

提醒:node build.js 会重写 cat-chat-all-in-one.js,属于「修改文件」。接手后若要跑,请先 git status 确认无未提交改动,或先建分支。


04 · 技术架构与代码基线

04 · 技术架构与当前代码基线

基线时间:2026-10-27 00:45 | 只读核实,未改任何代码 金额/配额口径:Cloudflare Workers 免费版 KV = 每天 1000 次 list、1000 次写、10 万次读(账户级,北京时间 08:00 重置)


1. 仓库与工作区

项 值 证据
本地目录 /workspace/chat 【实测】
远程 github.com/beibbloveme-crypto/yuai git remote -v【代码】
分支 main(当前)、fix/p0-memory-consistency(历史分支) git branch -a【实测】
HEAD 5666a72(2026-10-25 20:36)fix(kv): 修复每日 list 配额烧穿导致聊天 500 git log【实测】
工作区 干净(无已修改文件);未跟踪:handoff-public.md、小手机-完整交付文档.md、2 张 PNG git status【实测】
提交总数 60(10-05 起) git rev-list --count HEAD【实测】
远端同步 本地 main 已包含 10-25 的提交;远端实时状态未验证(沙箱访问 GitHub 受限) 【未验证】

2. 文件与职责

文件 行数 / 大小 角色 纪律
index.html 12,509 行 / 230,966 B 前端唯一真源 改完必须 node build.js
worker.js 2,801 行 / 152,446 B 后端唯一真源 同上;serveFrontend 由构建期注入,源码里未定义
build.js 58 行 构建脚本 worker.js + base64(index.html) + serveFrontend() → 产物
cat-chat-all-in-one.js 2,822 行 / 460,954 B 部署产物 禁止手工编辑
wrangler.toml — v2 配置(zhai-v2 + KV 0a4c…) 含真实账号/KV ID,勿外传
wrangler.v1.toml — v1 配置(zhai + KV ec1a…) 同上
deploy-web/server.js 484 行 国内版平台垫片 + 快照管理 只做适配,不做业务
deploy-web/kv-sqlite.js 155 行 env.CHAT 的 SQLite 实现 对齐 KV 六方法
deploy-web/worker.mjs 2,822 行 / 460,954 B 后端部署副本 与 bundle 逐字节相同(手工同步)
deploy-web/start-domestic.sh — 看门狗(退出 3 秒重启 + pidfile) —
tests/*.mjs 10 个文件 契约测试 全部 Mock,非 E2E
tools/*.mjs 3 个 搬迁/审计脚本 一次性工具

3. 构建链

index.html ─┐
            ├─ node build.js ─► cat-chat-all-in-one.js ─┐
worker.js ──┘                                           ├─ wrangler deploy (v2)  → zhai-v2
                                                        └─ wrangler deploy --config wrangler.v1.toml → zhai

index.html ──► deploy-web/server.js 运行时读取(注入令牌/WEB_MODE/sharecode 垫片)
worker.js ──► cp 成 deploy-web/worker.mjs ──► Node 动态 import

已知脆弱点: - wrangler deploy --config wrangler.v1.toml 不会自动构建 → 忘记 node build.js 就部署旧产物; - deploy-web/worker.mjs 没有自动同步步骤(当前一致,靠人工); - worker.js 单独 import 会因 serveFrontend 未定义在 GET / 时报错。


4. 运行时拓扑

┌──────────────── 浏览器(手机) ────────────────┐
│ index.html(单页,内联 CSS/JS)                 │
│ localStorage:设置/外观/模型 Key/worker_url     │
│ IndexedDB(cat_chat_db/kv):聊天记录、设备快照    │
└───────┬─────────────────────────────┬──────────┘
        │ 聊天:CONFIG.WORKER_URL      │ 记忆/档案/删除:相对路径(同源)
        ▼                             ▼
┌──────────────────┐          ┌──────────────────────┐
│ Cloudflare Worker │          │ Node 垫片(server.js)  │
│ v1: zhai          │          │ + SQLite(/tmp/...db)  │
│ v2: zhai-v2       │          │ 复用同一 worker.js    │
└────────┬──────────┘          └──────────┬───────────┘
         ▼                                ▼
   Cloudflare KV                    SQLite(WAL)
   v1 ec1a / v2 0a4c                652 键
         │
         ▼
   大模型 API(OpenAI 兼容,Key 由前端提供 → Worker 代理)

5. 路由表(worker.js,三端共用)

路径 方法 鉴权 说明
/、未匹配 GET GET 否 返回页面(注入令牌)
任意路径 POST 是 聊天主接口(handleChat)
/manifest.webmanifest、/icon-192.png、/icon-512.png、/apple-touch-icon.png GET 否 PWA 资源(base64 内嵌)
/health GET 否 {ok, service, version:"2.0"}(版本号写死)
/init POST 是 空实现(兼容保留)
/backup GET 是 旧版导出(只含 mem)
/archive GET 是 档案导出;支持 keyword/from/to/speaker/limit/offset
/archive POST 是 档案幂等导入(按 mid)
/export/full GET 是 全量导出(msg/hl/exp/rel/mem)
/import/full POST 是 全量导入(dry_run、幂等、逐条 errors/warnings、墓碑跳过)
/highlights GET/POST/DELETE 是 高光;写入强制引用真实 msg:
/experiences GET/POST/DELETE 是 经历;source_mids 必须存在
/relations GET/POST/DELETE 是 关系;两端必须存在
/proactive POST 是 主动消息(3 次/天、间隔 ≥3h、按 profile)
/messages/delete POST 是 mode=soft\|hard、force;有引用 → 409
/snapshots、/snapshot/now、/snapshot/rollback、/sysinfo GET/POST 令牌 仅国内版(server.js 自处理)

6. KV / SQLite 键位图

键模式 内容 说明
mem:<profile>:<ts>_<rand> 长期记忆(role/content/ts/day_key/hour/period) 聊天检索的数据源
mem_<ts>_<rand> 旧格式(全局) 仅 owner 只读兼容
msg:<profile>:<mid> 结构化档案(含 deleted_at) 导出/恢复/删除的对象
hl:<profile>:<hid> 高光原话(配 mid、content_hash) 记忆库 UI
exp:<profile>:<eid> 经历(active/candidate、source_mids、确定性) 记忆库 UI
rel:<profile>:<rid> 关系(5 种类型) 记忆库 UI;不进提示词
tomb:<profile>:<id> 墓碑(400 天 TTL,只存编号) 硬删防复活
meta_lastTs:<profile> / meta_lastTs 最后聊天时间 后者为旧键
proactive:<profile>:<day> / proactive_<day> 主动消息每日计数 26h TTL
rl_<hash> / rl_global 限流计数 2 天 TTL

<profile> 默认 owner,校验 ^[a-z0-9_]{1,32}$。


7. 三端配置现状(值已脱敏)

项 v1 zhai v2 zhai-v2 国内版
KV 绑定 CHAT = ec1a…7accc CHAT = 0a4c…ce8ad CHAT = SqliteKV 实例
Secret DEEPSEEK_API_KEY(代码不读) AUTH_TOKEN 无(AUTH_TOKEN 由 makeEnv 注入)
令牌 代码默认值(源码内) secret(页面注入) 代码内 cat-web-token
vars API_URL/ALLOWED_API_HOSTS/ALLOW_CUSTOM_API=0/ALLOWED_ORIGINS 同上 + MODEL ALLOW_CUSTOM_API=1、STRICT_MID=1、MEM_READ_LEGACY=1
部署版本 a3ef2a33(10-25 22:54) 8c6fe98a(10-25 20:38) 进程 22:26 启动
代码一致性 线上 = 本地(含修复)【线上核验】 同左(etag 相同) worker.mjs = bundle 逐字节【实测】

8. 数据面(截至 2026-10-27 00:45)

8.1 国内版 SQLite(/tmp/cat-chat-data/chat.db,652 键)

家族 数量 备注
msg: 320 307 条迁移导入 + 13 条 10-25 新聊
mem: 312 迁移带入
hl: 2 —
exp: 1 —
rel: 0 —
tomb: 0 硬删功能尚未在此库使用过
meta 2 meta_lastTs 与 meta_lastTs:owner
proactive 1 —
rl 14 限流计数残留

被删状态:320 条中 279 条带 deleted_at(10-23 22 点 68 条、10-24 13 点 211 条),有效 41 条。 迁移来源:307 条的 mid 前缀 = local_ 217 / c_ 44 / a_ 39 / u_ 7 → 大多数来自本机历史的「云端同步」上传,不是服务端实时聊天记录。 时间跨度:2026-10-14 02:40 → 2026-10-25 22:37。

8.2 Cloudflare KV

库 内容 证据
v1 ec1a… 抽样 4 个 msg:owner:*(local_/c_ 各 2)全部 200 命中;meta_lastTs:owner 读出 10-25 22:31 的时间戳 【实测】
v2 0a4c… 同 4 个键全部 404;meta_lastTs 停在 10-25 18:56 【实测】

精确键数无法枚举:免费版 list 配额已耗尽(code 10048),REST keys 同样被拒。以上为抽样结论,不是全量统计。

8.3 浏览器

每一台设备各有一份 IndexedDB(cat_chat_db/kv,键 messages),含图片 base64;云端不存图。


9. 测试覆盖

文件 断言数 覆盖
test_p0_memory_isolation.mjs 15 profile 隔离(含图片/主动/关键词重叠/旧数据)
test_p0_mid_refs.mjs 21 mid 回写、重试幂等、409、引用校验
test_p0_delete_sync.mjs 36 软/硬删、409、级联、墓碑、防复活
test_p0_export_import.mjs 27 全量导出/导入、dry_run、幂等、错误报告
test_p0_kv_quota.mjs 17 list 缓存、降级、503、写去重
test_p1_memory_context.mjs 13 高光/经历进 prompt、候选分离
test_p1_archive_search.mjs 14 搜索/时间/说话方/分页/软删
test_phase4_v2.mjs 32 阶段 4 回归(源码 + 产物各 16)
test_kv_sqlite.mjs 23 SqliteKV 六方法
test_ui_delete_scope.mjs ~8 删除范围 UI 契约(静态断言)
合计 约 206 全部为 Mock / 静态测试,非生产 E2E

未覆盖:CORS 跨源、令牌注入、WORKER_URL 覆盖、三端联调、真实上游、浏览器 E2E、配额恢复后的行为。


10. 依赖与成本

项 说明
运行时依赖 无 npm 依赖(Workers 原生 + Node 内置 node:sqlite)
外部服务 Cloudflare Workers/KV;国内托管平台(WorkBuddy);大模型 API(用户自选)
成本结构 KV 免费额度(list/写各 1000/天);模型费用由用户 Key 承担
实测约束 一次聊天约 5–6 次 KV 写、约 300 次读、1–2 次 list(热缓存 0–1 次)

11. 与原设计的偏差

设计 现状 偏差原因
D1 或 KV 选 KV 用户选型
单一 Worker v1 + v2 + 国内版三端 重建 + 国内可用性需求
「外置记忆库,原文入库」 mem: 与 msg: 双写 分阶段演进叠加,未合并
「云端完整档案」 图片只存占位 未接 R2
「管理后台 + 强认证」 无 阶段 7 未开始
「人设版本管理」 一段文本 阶段 5 未开始

05 · 记忆与收藏系统

05 · 长期记忆与收藏系统历史档案

本文件把「设计过的」「做出来的」「还没做的」分开记录,不合并、不互相覆盖。 现状基线:5666a72 / v1 a3ef2a33 / v2 8c6fe98a / 国内版 22:26 进程。


一、五代演进(设计层)

v0 · 原始记忆库(2026-10-05,✅ 已实现)

用户原始要求(原文):

「外置记忆库:后端必须接入 Cloudflare D1 或 KV。每次用户输入和 AI 回复原封不动存进数据库,带上时间戳。」 「检索逻辑:每次发消息时,去数据库检索最相关的历史原话,配合人设打包给大模型。」

实现: - 键 mem_<ts>_<rand>,值 {role, content, ts, day_key, hour, period}; - 检索:list(prefix:'mem_') → 取最近 300 条 → 中文双字滑窗 + 英文整词 tokenize → 命中数排序 → top5; - 时间感知:前端带 time.iso,后端 resolveTime() 归一,提示词注入「现在几点/什么时段」。

局限(后来被 P0-1 修掉):所有角色共用同一前缀,测试角色会读到正式角色的记忆。

v1 · 按 profile 隔离(2026-10-19,✅)

v2 · 结构化档案(2026-10-19,✅)

v3 · 高光 / 经历 / 关系(2026-10-19,✅)

类型 键 必填 进模型上下文?
高光原话 hl:<profile>:<hid> mid(真实消息)、content 必须能在原文中找到 ✅ top3
经历 exp:<profile>:<eid> title/content/source_mids、status=active\|candidate、time_certainty ✅ active top3 + candidate top2(候选明确标注「不得当作事实」)
关系 rel:<profile>:<rid> from_id/to_id(高光或经历)、5 种类型 ❌(仅 UI 查看)

删除级联:删高光/经历会软删引用它的关系;删消息时若有引用 → 409,确认后级联。

v4 · 框架升级构想(2026-10-23,⬜ 只调研,未实施)

用户下发《基线审查任务书》,要求研究 4 个开源项目并说明「可借鉴什么/对应源码位置/兼容性/成本/风险」,明确不得直接照搬、不得只看 README。

项目 机制要点 当时建议
Mem0 ADD/UPDATE/DELETE/NONE 四态决策 + history 表(每次变更留旧值)+ BM25 加性打分 建议 1:确定性规则 + history 版本链(每条记忆保留变更历史)
Graphiti 双时间戳 valid_at/invalid_at/expired_at + RRF 混合检索 建议 2:双时间戳(事件发生时间 vs 记录时间分离);建议 3:RRF 融合关键词与时间衰减
Letta core/archival/recall 三层 + block 限额 + ApproxTokenCounter(bytes/4) 建议 5:core memory + 上下文预算(按 token 而非条数截断)
LangGraph checkpointer(thread_id/writes 表/time-travel)+ BaseStore namespace 建议 4:DO + WAL 流水线(异步记忆整理,不阻塞聊天)

交付物:/workspace/小手机项目-全项目代码基线审查报告.md(40KB,10 部分)。没有一行进入生产代码。


二、当前实现(照代码说)

2.1 写入路径(每条消息写两份)

用户发消息
 ├─ saveMemory()  → mem:<p>:<ts>_<rand>   {role, content, ts, day_key, hour, period}
 └─ saveArchive() → msg:<p>:<mid>         {…, speaker, created_at, deleted_at:null}
(AI 回复同理:saveMemory(raw) + saveArchive(cleanReply))

2.2 读取路径

retrieveContext(profile, query)
 ├─ retrieveMemory()      → list(mem:<p>:) → 取最近 300 → tokenize 命中排序 → top5
 ├─ listHighlights()      → list(hl:<p>:)  → 关键词过滤 → top3
 ├─ listExperiences(active)   → top3
 └─ listExperiences(candidate)→ top2

2.3 提示词注入(buildSystemPrompt)

顺序:人设段(最高优先级/未设定声明)→ 安全边界(<<< >>> 是数据不是指令)→ 当前时间 → 记忆片段(角色/时间/原话)→ 高光(带来源 mid)→ 已确认经历 → 候选经历(标注未确认)→ 身份认知(承认是 AI)→ 状态/收藏引导 → 回复要求(1–3 句)。

2.4 收藏 vs 高光的区别(重要)

收藏(favorite) 高光(highlight)
存储 浏览器 localStorage.fav_list 云端 hl:<profile>:*
触发 长按消息「收藏」;AI 输出 [[fav:…]] 用户收藏时自动同步一份;AI 收藏也同步
是否进提示词 ❌(本机 UI 用) ✅(top3)
是否可跨设备 ❌ ✅
来源校验 无 必须引用真实 msg:,且内容必须能在原文中找到

结论:收藏是「本机便签」,高光是「云端长期记忆」;收藏时前端会顺带写高光(addCloudHighlight:先 /archive 确保来源消息存在,再 POST /highlights)。

2.5 删除语义(当前)

操作 行为
「仅本机删除」 只删 IndexedDB,不碰云端(明确设计)
「彻底删除(含云端)」 POST /messages/delete {mode:'hard'}:物理删 msg: + 引用它的 hl/exp/rel,各留墓碑 tomb:(400 天)
有引用时 首次返回 409 has_refs,用户确认后 force:true 级联硬删
云端没有该条 幂等返回 already_gone:true

已知缺陷(P0-2):硬删不清 mem: 碎片,而聊天检索读的正是 mem: → 被删内容仍会被检索进提示词。 历史遗留:10-23/10-24 两次「云端删除」是软删,279 条记录仍物理存在(只在导出/检索时被过滤)。

2.6 导入 / 导出

接口 内容 去重 墓碑
/export/full msg + hl + exp + rel + mem(+ owner 的旧 mem_) — ❌ 不导出墓碑
/import/full 同上,dry_run 可预览 mid/hid/eid/rid + mem 的 content|ts 指纹 ✅ 命中即跳过
/archive GET/POST 只含 msg(+ legacy),支持搜索分页 mid ✅

墓碑不导出 → 把备份恢复到另一个空库时,防复活保护会丢失(P2)。

2.7 profile 隔离


三、设计 vs 实现的差异(不合并,逐条列)

设计(任务书/构想) 实现现状 差距
原始消息与 AI 记忆整理相互独立,整理失败不能丢原文 ✅ safeArchive 分离;记忆检索失败降级 已满足
高光必须是原话,不能 AI 改写 ✅ content 必须被原文包含(STRICT_MID) 已满足
删除高光不删原始消息 ✅ 只删 hl + 级联关系 已满足
派生记忆必须有来源 ✅ source_mids 必须全部存在 已满足
不确定信息不得当事实 🟡 candidate 单列 + 提示词标注;但没有审核流转 无「确认/驳回」动作
记忆可更新/合并 ❌ 只能新增与删除;没有 update/merge 与 Mem0 四态差距最大
记忆变更历史 ❌ 无 history 表 建议 1 未实施
双时间戳(事件时间/记录时间) 🟡 经历有 event_time + time_certainty,消息只有 created_at 不完整
Token 预算 ❌ 按条数截断(5/3/3/2),无 token 计数 建议 5 未实施
记忆异步整理(不阻塞聊天) ❌ 同步写两份 建议 4 未实施
向量检索 ❌ 关键词子串匹配 明确非第一阶段必要
跨设备收藏 ❌ 收藏只在本机 高光可跨设备

四、成本与配额影响(记忆系统的真实代价)

操作 KV 读 KV 写 list
一条聊天(冷缓存) 约 300(mem 最近 300 条)+ 若干 5–6 4–5
一条聊天(5 分钟内热缓存) 约 300 5–6 0–1
一次「彻底删除」 与 hl/exp/rel 数量成正比 2 + 引用数 2(hl/exp 原始 list)
一次全量导出 与总记录数成正比 0 每 200 键 1 次
一次全量导入 与总记录数成正比(含指纹预扫) 与新增数成正比 2–3

免费版上限(账户级/天):list 1000、写 1000、读 10 万。 → 约 160 条聊天/天 会打满写配额;333 条/天 会打满读配额;密集同步/搬迁是 list 的主要杀手。 2026-10-25 的 500 故障就是 list 1000 次被烧穿(见 07 事件 06)。


五、必须保留的东西(接手时不要动)

  1. mem: / msg: / hl: / exp: / rel: / tomb: 六类键的命名与前缀语义(有存量数据)。
  2. STRICT_MID 与 MEM_READ_LEGACY 两个开关的默认值(1/1)与含义。
  3. content_hash = strHash(profile + '|' + content) 的去重算法(改了会重复导入)。
  4. 墓碑 400 天 TTL 与「导入前查墓碑」的检查顺序。
  5. 高光「内容必须被原文包含」的校验(这是防「AI 改写冒充原话」的核心)。
  6. profile 默认 owner 与校验正则。

六、最需要先决定的三件事

  1. 检索数据源统一:继续双写 mem:+msg:,还是让检索改读 msg:(顺手解决 P0-2)?
  2. 历史 279 条软删怎么处理:硬删+墓碑(要分批、耗写配额)还是保留但标记?
  3. 记忆要不要升级到 v4(更新/合并/history/双时间戳):这决定阶段 6「成长分析」有没有地基。

06 · 决策记录与版本变化

06 · 决策记录与版本变化

分类严格区分: 【用户确认】 = 用户明确说「可以/同意/就这么做」或直接下达; 【用户提出】 = 用户提出方向但未最终拍板; 【AI 建议】 = 我提出、用户没有明确确认; 【已取代】 = 后来的要求覆盖了旧决定(旧记录保留,不静默删除)。


一、决策表

ID 时间 问题 用户原话/要求 最终决定 依据/原因 状态
D-01 10-05 模型 Key 放哪 (AskUserQuestion 选择) Worker 统一转发,Key 不进前端直连 安全 【用户确认】仍有效
D-02 10-05 检索方式 (选择) 关键词匹配,不上向量库 零成本、够用 【用户确认】仍有效
D-03 10-05 主动消息触发 (选择) 页面打开/回前台时检查,不用 Cron 无需定时任务 【用户确认】仍有效
D-04 10-05 是否重构 「绝对不准重构我的代码结构,必须保留现有布局」 只做增量补丁,按区域改 用户在手机上操作,怕失控 【用户确认】仍有效,最高优先级
D-05 10-19 默认人设 要求取消「小喵」默认人设 只用自定义人设;无则明确告知模型「未设定」 用户不想被内置角色绑定 【用户确认】仍有效
D-06 10-17 深色模式 要求删掉入口 移除 UI 入口(CSS 变量残留) 简化 【用户确认】仍有效
D-07 10-14 手绘气泡 参照截图做装饰气泡 上线后中文乱码 → 回滚 atob 字节处理错误 【已取代】不再尝试
D-08 10-24 02:55 后端总出错 「聊天记录没有就没有了……全部重新部署吧一个全新的」「我的意思是前后端全部都是新的」 新建 zhai-v2 + 新 KV chat_memory_v3(空库),旧 zhai/chat_memory_v2 保留不删 用户要干净环境;旧库留作退路 【用户确认】仍有效
D-09 10-24 18:00 国内能不能用 「暂时先部署到 WorkBuddy 上面,后端那个记录的先不用」 国内直连版先不落库,只做代理 先验证可用性 【已取代】→ D-10
D-10 10-25 04:40 后端迁哪 「理论上……迁移到阿里云?」→ 四方案 → 「你先试一下 C 吧」 C 方案:WorkBuddy + Node 垫片 + SQLite;先验证持久性 成本最低、改动最小 【用户确认】仍有效
D-11 10-25 04:38 极端场景 「情况一跟情况二的话,你这两个你都考虑一下,然后做一个版本」 双快照三层防护:服务端每日快照(7 份)+ 设备每日快照(3 份)+ 手动导出导入 防平台清盘/手滑/换设备 【用户确认】已实现
D-12 10-25 19:00 云端删除是假的 「我发现这个云端删除它不能真的云端删除」→ 选 A 根治 物理删除 + 墓碑防复活 兑现「本机和云端都删」的 UI 承诺 【用户确认】已实现
D-13 10-25 18:32 灰度顺序 「这个改进的你先做那个 V2 的……万一跑不通怎么办?」 v2 先行验收,再 v1,再国内版 怕一次全改跑不通 【用户确认】已执行(v2→v1;国内版随代码自动生效)
D-14 10-25 22:32 500 是不是额度问题 「意思是…额度没了?你确定么」 复核证据:两库 list 同报 10048、读正常 → 确认是账户级 list 配额;修复=缓存+降级+干净 503 用户要求对质证据 【用户质疑→证据确认】已实现并部署
D-15 10-25 23:23 审查范围 「立刻停止所有修改动作!进入只读审查模式」 只读审查三端,出报告,不改码 用户要决策依据 【用户确认】已执行
D-16 10-26/27 交接 要求整理全部历史,给新 AI 生成 10 份交接档案 换 AI 接手 【用户确认】本次执行

二、AI 提出但用户没有确认的建议(不要当成已定)

ID 建议 状态 备注
S-01 图片存 R2(云端保留原图) 未确认 涉及付费;当前图片只在本机
S-02 用 Cloudflare Access / 登录态替换「令牌随页面下发」 未确认 P0-3 的推荐解
S-03 记忆框架升级(Mem0 四态 + history / Graphiti 双时间戳 / Letta 预算) 未确认 只有调研报告
S-04 检索改读 msg:、停写 mem:(解决删除不彻底) 未确认 P0-2 的方案 A
S-05 历史 279 条软删分批硬删 + 墓碑 未确认 需写配额预算
S-06 轮换 v2 的 AUTH_TOKEN(因明文写在 index.html) 未确认 换令牌后页面自动注入,无需改前端
S-07 统一备份口径(v1/v2 也支持 schema:2 导入) 未确认 P1-4
S-08 删除设置里的「Worker 地址」输入(永远同源) 未确认 P0-1 的方案 B
S-09 把 deploy-web/worker.mjs 改成构建时自动复制 未确认 P2-2
S-10 阿里云 ECS / 国内 Serverless 未采用 用户选 C

三、版本与线上变化(可核对)

时间 版本/提交 变化 影响
10-05 基线 afa0aa4 初版全栈 —
10-14 手绘气泡 → 回滚 乱码事故 用户看到「乱码」
10-16~17 08815a9、7702738 等 UI v2、PWA、安全 v2 46 项安全断言
10-19 9505597…b03a612 P0-1~P0-4 + P1 145 项断言
10-22 f02cc07 / 线上 51ac31d3 iOS 键盘最终版 用户确认「终于行了」
10-23 9ed08a6 / 线上 0114d69f 删除+输入框修复 —
10-24 02:59 867faee 新建 v2(zhai-v2 + chat_memory_v3) 空库
10-24 18:32~19:11 c007087、be64807、bd6a5af 国内版三连修 sharecode/默认地址/人设
10-25 04:33~05:37 aa3998b…a32fe2e C 方案 1–3 批 SqliteKV、垫片、快照
10-25 09:01 5f61f81 第 4 批搬迁 606 条 国内版有数据
10-25 13:33~15:31 b885e6e、0f1007c、dfeff1d、32aa8a5 同源默认、403 修复、v1 令牌注入 v1 401 修复
10-25 18:48 83bc573 硬删+墓碑(先 v2 65bf6ef0) 删除语义变更
10-25 20:36 5666a72 配额修复 缓存+降级
10-25 20:38 v2 8c6fe98a 部署配额修复 —
10-25 22:54 v1 a3ef2a33 部署配额修复 用户主入口恢复

四、被取代/撤销的旧决定(保留记录)

旧决定 取代者 说明
手绘气泡装饰 无 乱码后回滚,未再提
深色模式 无 用户要求删除入口
默认人设「小喵」 只用自定义人设 10-19
软删即「云端删除」 硬删+墓碑 10-25
国内版「不落库」 C 方案 SQLite 10-25
单一 Worker v1 + v2 + 国内版 10-24 起
Pages 静态托管 三端自托管页面 Pages 项目 chat-ui-beib 实为 10-03 的另一原型
deploy.sh 手工改产物 build.js 整体重生成 10-19
迁移只搬 v2 两库合并(v1+v2) 用户 10-25 指出「搬错了有是 v1 的」

五、记录冲突(保留双方,标记待核实)

冲突 A 说法 B 说法 处理
v1 库在 10-24 是否有数据 10-24 02:35 审计:msg:owner 仅 1 条、mem:owner 0 条、11 条旧 mem_* 10-27 实测:4 个 msg:owner:* 抽样全部命中,含 217 条 local_ 前缀 结论:v1 在 10-24 之后被本机历史「云端同步」填充;具体是哪台设备/哪个入口触发待确认
搬迁数量 提交信息「606 条」 国内版实际:307 消息 + 299 记忆(312 mem 含后续新增) 606 = 307+299,口径是「消息+记忆」,不是 606 条消息
国内版「搬错了」 用户:搬的是 v1 的数据 迁移工具设计:两库合并 事实成立:工具第 45 行同时读 0a4c 与 ec1a,v1 数据被一起带入;是否「错」取决于用户想要哪份
v2 空库 我早期判断:搬迁失败 用户:全新部署,本来就空 以用户为准
DEEPSEEK_API_KEY wrangler.v1.toml 注释:secret 不随部署变动 代码只读 env.API_KEY 死配置,见 07 P1-1
「前后端是一套」 我早期表述 用户:前后端分开且不一致 以用户为准:两份真源 + 两套寻址规则

07 · 问题风险与失败经验

07 · 问题风险与失败经验

原因分级:【已证实】 有日志/复现/文档证据 |【高度可疑】 证据链完整但缺最后一环 |【未查明】 无法复现或无线索。 原则:不把偶发问题直接定性成「代码故障/数据丢失/安全事件」。


一、事故与故障档案

事件 01 · 手绘气泡上线后中文乱码(10-14)【已证实】

事件 02 · 发送键把点击事件当消息(10-17)【已证实】

事件 03 · iPhone 底部布局/键盘连续 6 轮返工(10-22)【已证实】

事件 04 · 部署产物与源码漂移(10-19 前)【已证实】

事件 05 · 国内版请求穿墙打 v2(10-25 凌晨)【已证实,我方引入】

事件 06 · 国内版记录类接口全 403(10-25)【已证实】

事件 07 · 国内版人设丢失、AI 自认 DeepSeek(10-25)【已证实】

事件 08 · v1 页面 401(令牌不匹配)(10-25)【已证实】

事件 09 · 删除后云端「复活」(10-24 前)【已证实】

事件 10 · 「云端删除」其实只是软删(10-25)【已证实】

事件 11 · v1/v2 聊天全部 500(10-25 14:30–22:30)【已证实(主因)】

事件 12 · 国内版「搬迁后不能用了」(10-25 12:58)【部分查明】

事件 13 · pgrep -f 误杀自己(10-19)【已证实】

事件 14 · 沙箱访问不了 workers.dev(长期)【环境限制】


二、当前风险登记(按严重度)

完整版与修复方案见 /workspace/审查报告-三端代码与风险-20261025.md;此处只列结论。

P0(致命,必须马上处理)

ID 风险 后果
P0-1 前端「聊天」走 CONFIG.WORKER_URL,「记忆/档案/删除」走相对路径 一个设置就能让读写分裂到两个后端;「删了 AI 还记得」
P0-2 硬删只删 msg:,不删 mem:(检索读的正是 mem:) 「彻底删除」名不副实,被删内容仍进提示词
P0-3 页面公开 + 令牌随页面下发 拿到 URL 即可读走/删光全部记忆

P1(严重)

ID 风险
P1-1 v1 的 DEEPSEEK_API_KEY 代码不读;v2 无模型 Key → 换设备首聊必失败
P1-2 检索只扫「最旧 1000 个」mem 键,超 1000 后新记忆静默失效
P1-3 国内版数据与快照全在 /tmp;同源基准写死,换域名全 403
P1-4 备份口径三端不一致(v1/v2 只能导本机,云端档案只有国内版能导)
P1-5 跨源响应缺 Access-Control-Allow-Origin(json() 没带)
P1-6 图片不入云,换设备即永久丢失,备份也不含
P1-7 首次云同步 O(n) 写,1000 条一次打满日配额

P2/P3(摘要)


三、不要重复的操作(血泪清单)

  1. 不要手工编辑 cat-chat-all-in-one.js;一律改 worker.js / index.html 后 node build.js。
  2. 不要跳过 build 直接 wrangler deploy --config(会部署旧产物)。
  3. 不要在没备份时 clearAll() / 清 KV / 清 IndexedDB。
  4. 不要用 pkill -f / pgrep -f 匹配 server.js(会杀自己)。
  5. 不要一次硬删超过 200 条(写配额 1000/天;还要留余量给聊天)。
  6. 不要在配额耗尽时反复重试(每次重试都在烧 list/写;新代码已降级,但旧页面会继续打)。
  7. 不要用「沙箱打不开 workers.dev」判断线上故障。
  8. 不要把「v2 空库」当成故障(那是按用户要求新建的)。
  9. 不要在页面里写死后端地址或令牌(v1 401 与国内版穿墙都源于此)。
  10. 不要维护第二份业务逻辑(国内版早期 handleChat 副本导致人设丢失)。
  11. 不要相信「UI 有按钮 = 功能通」(云端删除、全量导入都曾如此)。
  12. 不要只跑 Mock 测试就宣称线上可用。

四、仍然未闭环的三个问题

# 问题 缺什么 怎么补
1 10-25 第一次 500 的确切异常文本 线上日志(wrangler tail 不可用) 下次故障时让用户截图 rid,或接 CF Logpush
2 v1 库在 10-24 之后被谁/何时填充 无部署/同步日志 看 v1 meta_lastTs 时间线 + 用户回忆;或等配额恢复后按 created_at 抽样
3 10-25 12:58 国内版「用不了」的完整原因 当时的服务日志已滚动 保留 /tmp/cat-chat-server.log;下次复现立即抓

08 · 下一阶段执行路线

08 · 下一阶段执行路线

排序原则:先止血 → 再修数据正确性 → 再补部署韧性 → 最后做任务书阶段 5–9。 每个阶段都标注:前置条件 / 要动的文件 / 验收 / 回滚 / 是否需要用户批准。 ⚠️ 本文件只是路线,不代表已经开工。


阶段 0 · 只读调查(无需批准,随时可做)

# 事项 方法 产出
0.1 确认三端是否仍跑同一份代码 CF API 拉 zhai/zhai-v2 bundle,与本地 cat-chat-all-in-one.js 比对;国内版 GET / 看 data.notice 一致性结论
0.2 等 KV list 配额恢复(北京时间 08:00 后),枚举 v1/v2 精确键数 REST keys 分页(注意:每次调用都消耗配额,分批) 数据量基线
0.3 核对 v1 的 307 条与国内版 320 条的差集 按 mid 对比(导出两份 id 列表) 哪些只在一边
0.4 查清 v1 数据是何时/哪个入口同步进去的 msg 的 created_at 分布 + source_id + meta_lastTs 历史 数据来源结论
0.5 复核 279 条软删的内容与时间分布 只读 SQL / KV 抽样 清理清单

不做:不写库、不删键、不部署。


阶段 A · 止血(1 次部署,约 30 分钟)

项 内容
目标 修掉「一改就全坏」的低级陷阱,不改业务语义
前置 无
文件 worker.js(json() 增加 request/env 参数并合并 corsHeaders;validateApiUrl 保留 query;env.API_KEY \|\| env.DEEPSEEK_API_KEY;serveFrontend 桩)、index.html(无需改)
具体 ① P1-5 CORS ② P1-1 Key 兼容 ③ P2-5 query ④ P2-13 兜底函数
验收 跨源 OPTIONS + POST 都有 ACAO;?a=1 的 API URL 不被截断;先 node build.js 再部署;/health 200;本地 10 个测试全过
回滚 wrangler rollback <上一版本>;v1 a3ef2a33、v2 8c6fe98a
批准 需要(涉及部署)

阶段 B · 数据正确性(本阶段最关键,必须先做决策)

B0 先决策(用户拍板,二选一)

选项 含义 代价
B-方案 1(推荐) 检索数据源统一到 msg:,mem: 停写、只读兼容 改动中等;需一次性对齐存量;删除天然彻底
B-方案 2(保守) 保留双写,硬删时扫 mem: 找 mid 删除 改动小;每次删除多 1 次 list;库越大越慢

B1 修 P0-2(彻底删除)

B2 修 P0-1(前后端寻址统一)

B3 修 P1-2(检索 1000 键截断)


阶段 C · 部署韧性(国内版优先)

# 事项 前置 文件 验收
C1 P1-3 数据落持久盘 + 快照异地 平台是否有持久卷 server.js、start-domestic.sh 重启容器后 kv.count() 不变;快照可下载
C2 P1-3 同源基准兜底 无 server.js(PUBLIC_ORIGIN 读取顺序 + 403 中文页) 换域名访问不再「发送失败」,而是明确提示
C3 P1-4 备份口径统一 无 index.html(去掉 isWebMode 限制)、worker.js(无需) v1/v2 也能导出/导入 schema:2
C4 P1-7 分批同步 无 index.html(cloudSyncToServer 分批 + lastSyncTs) 1000 条同步不一次打满写配额
C5 P1-6 图片策略(先决策) 用户决定:接受 / R2 / 缩略图 index.html 文案;R2 需 worker.js + 绑定 用户明确知道「图不同步」或图可恢复

阶段 D · 访问控制(P0-3)

选项 工作量 说明
D-A Cloudflare Access(推荐) 小 在 CF 面板给 zhai/zhai-v2 加策略,只允许指定邮箱;国内版视平台能力
D-B 自建口令登录 中 POST /login → 签名 HttpOnly cookie;页面不再注入 token;三端同款
D-C 最低成本 小 页面不再明文注入 token,改「首次输入访问码」存 localStorage;GET / 也只返回壳页

阶段 E · 历史数据清理(279 条软删)

项 内容
前置 阶段 A 完成;配额充足;先导出全量备份
步骤 ① 导出 /export/full 存档 ② 生成待删清单(mid)③ 每批 ≤200 条:POST /messages/delete {mode:'hard', force:true} ④ 每批后抽查墓碑与残留 ⑤ 记录每批结果
配额 每条硬删 ≈ 2 写 + 1–2 list;200 条约 400–600 写 → 一天最多 1 批
验收 tomb: 数量 = 279;msg: 剩 41 条;/export/full 的 counts 与预期一致
回滚 备份可 /import/full 恢复(但墓碑会阻止恢复被删项——这正是设计目的;如需恢复须先删对应墓碑)
批准 需要(不可逆)

阶段 F · 任务书阶段 5–9

阶段 先决条件 主要产出 验收要点
5 人设版本 决定人设存储结构(KV persona:<p>:<ver> + 当前指针) 版本列表/对比/回滚;聊天只用「当前版本」 核心人设不被单次聊天覆盖;改动留版本
6 成长分析 阶段 5 完成;记忆可更新(v4 能力) 定时任务(CF Cron 或国内 cron)+ 候选建议 + 审核队列 不在每轮聊天跑;失败可重试;建议带证据/反例;未审核不生效
7 后台管理 阶段 D 完成(强认证) 档案/记忆/人设/任务/备份的移动端管理页 有鉴权、有操作日志、关键操作二次确认
8 沈屿迁移 阶段 5 + 7;旧数据已整理 导入真实历史/人设/记忆;角色隔离 数量与内容核验;记忆可追溯来源;备份恢复实测
9 测试清理 阶段 8 验收通过 删测试 profile 数据与测试专用资源 正式数据完整;测试数据不参与检索;通用代码保留

给新 AI 的开工建议(按顺序)

  1. 先读:README_交接索引.md → 01 → 04 → 07(先知道哪里有坑)→ 03 → 05。
  2. 先只读核对:阶段 0 的 5 项,不要一上来改代码。
  3. 第一批改代码只做阶段 A(4 个小修),一次部署,观察 1 天。
  4. 等用户对 B0 拍板再做 P0-2/P0-1/P1-2;这三项会动检索与前端寻址,必须一起设计。
  5. 不要在没得到批准时:部署、清理数据、改密钥、动 v1 历史库。
  6. 任何批量操作都按「≤200 条/批、跨天、先备份」执行。

明确不建议现在做的事

事 原因
大规模重构 / 换框架 违反用户第一约束;现有三端在跑
直接把 mem: 全删 会丢历史记忆;先改读路径,再决定是否清
立刻上向量检索 / Mem0 成本与复杂度高;先把删除与检索正确性修好
把 v1 数据整体覆盖到 v2 用户没要求;v2 是「全新环境」的定位
用 R2 存图 涉及付费,未获授权

09 · 缺失资料与待确认

09 · 缺失资料与待确认事项

真实性规则:只写实际访问过的;「知道存在但没读」和「无法访问」分开列;不假装拥有完整历史。


一、我实际读过 / 核对过的资料

类别 资料 读取程度
对话历史 本会话全部消息 + 30 个阶段压缩摘要(2026-10-05 ~ 10-25) 摘要逐段读过;更早的逐字对话只存在于摘要中
原始需求 /root/uploads/1792388616892580314-文本.txt(任务书 v1.0,27KB) 读过背景、12 项目标、阶段 0–9、约束条款
原始源码 /root/uploads/1791183407093837939-0643203b2611b966_v6.html(56KB) 只读了摘要中记录的改造方式,未逐行重读
代码 worker.js(2801 行)全文 逐段读完
代码 index.html(12509 行) 读了配置区、请求层、发送、删除、同步、记忆库、启动等全部逻辑区;CSS 未逐行
代码 deploy-web/server.js、kv-sqlite.js、build.js、两份 wrangler 配置、start-domestic.sh 全文
代码 deploy-web/worker.mjs 与 bundle 逐字节比对(相同)
文档 小手机-完整交付文档.md(605 行) 全文
文档 任务交接说明.md(254 行) 全文
文档 模块索引.md、安全说明.md、修复记录-P0-P1.md、国内版落库迁移方案.md 读了章节结构与关键节
文档 小手机-全新后端环境说明.md 全文
文档 /workspace/小手机项目-全项目代码基线审查报告.md(40KB) 读了摘要与目录级信息
数据 国内版 SQLite(只读) 键分布、时间分布、mid 前缀、软删统计
数据 v1/v2 KV(抽样读) 4 个消息键 + meta_lastTs;无法枚举
线上 Cloudflare API:worker 列表、secret 名单、线上 bundle 已核对 v1/v2 版本与代码内容
线上 国内版 GET /、/health 已核对注入令牌与页面版本

二、知道存在、但没有读全的资料

资料 大小 为什么没读
/workspace/chat/猫爪聊天-完整项目.md 304KB 历史融合版全代码(占位符版),与当前代码重复
/workspace/chat/handoff-public.md 40KB 旧交接公开版,内容被 10-22 交付文档覆盖
/workspace/小手机-交接-公开版.md 40KB 同上
/workspace/chat/替换指南.md、给其他AI-代码包/、给其他AI-完整代码包.zip — 10-14 时期的历史交接材料
/workspace/小手机-审计草案/01–04*.md 37KB 只读了目录;内容已并入 P0/P1 修复记录
17 张界面截图 PNG — 视觉参考,不影响状态判断
/workspace/小手机交接/index.html(62KB)+ build_site.py — 10-22 的「交接网页」构建产物
tools/migrate_v2_to_domestic.mjs 全文 — 只读了关键行(双命名空间、/import/full)
tests/ 10 个文件全文 — 读了每个文件的头部说明与断言数量

三、当前无法访问的资料

资料 原因 影响
GitHub 远端实时状态 沙箱访问 GitHub 受限 无法确认远端是否与本地 5666a72 一致
Cloudflare 线上日志(wrangler tail / Logpush) 网络无法建 WebSocket 无法取 500 的原始异常文本
v1/v2 KV 的精确键列表 免费版 list 配额耗尽(10048) 只能用抽样与迁移结果推断总量
Secret 明文 CF API 不返回 只能确认名称,无法核对令牌值
用户浏览器 IndexedDB / localStorage 在用户设备上 无法确认本机历史是否比云端更全
平台(WorkBuddy)控制台与持久化策略 无访问权限 /tmp 是否跨容器重建持久,只能靠实测推断
早期逐字对话(10-05 ~ 10-22) 已被压缩成摘要 个别措辞与细节可能丢失;本档案以摘要为准并标注
聊天中出现过的截图(COS 链接) 部分链接已过期/需鉴权 无法复核当时的视觉问题

四、需要用户补充的材料

  1. 沈屿本体的资料(阶段 8 用):历史聊天导出、人设原文、重要经历清单——目前一份都没有。
  2. 你手机上当前的本机聊天记录是否比云端多?(决定「云端 vs 本机」谁是主副本)
  3. 10-24 之后你是否在 v1 页面点过「云端同步」?(决定 v1 那 307 条的来源解释)
  4. 国内版平台的 持久化承诺(是否会重建容器/清 /tmp)与可用存储路径。
  5. 你是否还需要 Pages 那个入口(当前三端都不用)。
  6. 图片:接受「只在本机」,还是愿意为 R2 付费?

五、需要你确认的决定(按影响排序)

# 问题 选项 我的建议
Q1 历史 279 条软删怎么办 A 分批硬删+墓碑 / B 保留但标记 / C 先导出再删 A(先导出)
Q2 检索数据源 A 统一到 msg:(推荐)/ B 保留双写+删除时扫 mem: A
Q3 「Worker 地址」设置 A 保留但全接口跟随 / B 删掉,永远同源 B(最少踩坑)
Q4 访问控制 A Cloudflare Access / B 自建登录 / C 暂不处理 A
Q5 主数据源 A v1 / B 国内版 / C 双主(需同步) 先定一个主,另一个只做备份
Q6 图片 A 接受本机 / B R2 / C 只存缩略图 先 A,明确告知
Q7 是否继续任务书阶段 5–9 继续 / 暂停 先修 P0,再谈阶段 5

六、安全事项(不含任何密钥值)

事项 现状 建议
Cloudflare API Token 曾写在文档里 任务交接说明.md 第 133 行有明文 token(早期文档) 轮换该 token;文档外发前删除该行
v2 AUTH_TOKEN 明文在 index.html 第 10 行 已提交进 git 历史 轮换 v2 令牌;页面改占位符;必要时清 git 历史
用户在对话中贴过模型 API Key 用户已表示会吊销 确认已吊销;不要写进任何文档
平台 sharecode 写死在 server.js 属平台访问凭据 视作敏感;换域名时同步更换
页面公开下发令牌 三端一致 见 Q4
wrangler.toml 含账号 ID / KV ID 仅本机 外发用占位符版

本档案及同目录其余 9 个文件均不含上述任何真实值。


七、资料之间的冲突(保留双方,待核实)

冲突点 说法 A 说法 B 状态
v2 为何是空库 我 10-25 判断「搬迁失败」 用户:「我让你重新部署的一个完全全新的」 以 B 为准;A 是误判
606 条是什么 提交信息「606 条」 307 消息 + 299 记忆 口径不同,非冲突
国内版数据来源 用户:「搬错了有是 v1 的」 工具设计:两库合并 事实是合并;是否算「错」取决于目标
前端后端是否一套 我早期表述「一体」 用户:「单独分开的,也是不一致的」 以 B 为准
v1 库 10-24 是否有数据 审计:几乎为空 10-27 实测:307 条在其中 中间发生了本机同步;触发者待确认
DEEPSEEK_API_KEY 是否生效 配置注释暗示生效 代码只读 env.API_KEY 以代码为准:不生效

八、如果只能问三个问题

  1. 主数据放哪:v1 还是国内版?(决定后面所有同步/清理的方向)
  2. 历史那 279 条删不删?(不可逆,必须先定)
  3. 要不要现在做访问控制?(P0-3,越晚风险越大)

其余都可以边做边定。


三端代码与风险审查(2026-10-25)

猫爪聊天 · 项目状态与风险审查报告

审查性质:只读审查(未修改任何一行代码、未改任何配置、未部署) 审查时间:2026-10-25 23:30(北京时间) 审查范围:v1(worker zhai)、v2(worker zhai-v2)、国内版(Node + SQLite)三套运行的完整代码与线上状态 审查方法:全量通读 worker.js(2801 行)、index.html(12509 行,重点逻辑区)、deploy-web/server.js、deploy-web/kv-sqlite.js、build.js、两份 wrangler 配置、start-domestic.sh、10 个测试文件;用 Cloudflare API 拉取线上真实 bundle 逐字节比对;只读方式查询国内版 SQLite 库;回看聊天记录核对需求演变


0. 先更正我此前两个错误结论

你提醒得对,我此前有两处判断是错的,先把账认了:

我此前的说法 事实 说明
「v2 是空壳,昨天的搬迁没落到 v2 KV」 v2 空是设计如此。10-24 深夜你让我重新部署一个完全全新的环境(worker zhai-v2 + 新库 chat_memory_v3),它本来就该是空库。 我把「按你要求新建的空环境」误判成「搬迁失败」。606 条从来不是要搬进 v2 KV,而是搬进国内版(SQLite)。搬迁工具 tools/migrate_v2_to_domestic.mjs 第 45 行同时读了 0a4c…(v3) 和 ec1a…(v2/v1 数据) 两个库,所以国内版拿到的是两库合并——这也是你说「你聊天库搬错了有是 v1 的」的原因:v1 的数据被一起搬进了国内版。
「前后端是一套」 前端与后端确实是两份独立真源,且存在口径不一致(详见 P0-1、P1-4、P1-5)。 前端 index.html 是唯一前端真源,后端 worker.js 是唯一后端真源;cat-chat-all-in-one.js = 后端 + base64(前端),是构建产物。但前端里「聊天」走 CONFIG.WORKER_URL,而「记忆库/归档/删除」走相对路径(永远打当前页面所在后端)——两套寻址规则并存。

1. 整体概览

1.1 三个运行版本对照

项目 v1 v2 国内版
入口 https://zhai.beibbloveme.workers.dev https://zhai-v2.beibbloveme.workers.dev https://a0cd1755f00a22a37.app.workbuddy.host
运行时 Cloudflare Worker Cloudflare Worker Node 22(server.js)+ 内置 SQLite
部署标识 版本 a3ef2a33(22:54 部署)etag b22ea7c0… 版本 8c6fe98a(20:38 部署)etag b22ea7c0… 进程 22:26 重启,已加载新代码
存储绑定 KV ec1a…(chat_memory_v2) KV 0a4c…(chat_memory_v3,全新空库) /tmp/cat-chat-data/chat.db(SQLite,652 键)
访问令牌 无 AUTH_TOKEN secret → 用代码内默认值 518effb1… AUTH_TOKEN secret(页面自动注入) 代码内固定 cat-web-token
模型 Key secret 名是 DEEPSEEK_API_KEY,代码只读 env.API_KEY,永远读不到(P1-1) 无任何模型 Key secret 无(ALLOW_CUSTOM_API=1,靠前端自填)
代码版本 与 bundle 一致,含配额修复 同左(etag 与 v1 相同) worker.mjs 与 bundle 逐字节相同,含配额修复
数据状态 真实历史主库(约 307 条归档 + ~300 条记忆碎片) 基本为空(10-25 傍晚使用过少量) v1+v2 合并导入 307 条 + 今天新聊 13 条 + 312 条记忆
与前端关系 页面由 worker 自己吐出(内嵌) 同左 页面由 server.js 吐出,读仓库里的 index.html
当前可用性 可聊(KV 配额耗尽 → 降级模式 + 提示) 可聊(同左,但库空) 完全可用(SQLite 无配额)

1.2 数据到底存在哪里

四份互不同步的数据副本:

# 位置 内容 性质
① v1 KV ec1a… 归档消息 msg:owner:*(约 307 条,其中 279 条带软删标记)、记忆碎片 mem:owner:*(约 300 条)、高光/经历/关系少量 云端主档(历史)
② v2 KV 0a4c… 全新空库,仅 18:56 前后少量新数据 云端新档(按你要求重建)
③ 国内版 SQLite msg 320 / mem 312 / hl 2 / exp 1 / meta 2 / rl 14 / tomb 0 国内直连主档
④ 每台浏览器的 IndexedDB 全量聊天记录 + 图片 base64(图片不入云) 本机主副本

关键事实(只读查询得出):

1.3 代码构成与构建链

index.html  (前端唯一真源, 12509 行, 230,966 字节)
     │
     ├── build.js ──► cat-chat-all-in-one.js  (= worker.js + base64(index.html) + serveFrontend)
     │                        │
     │                        ├── wrangler.toml      → worker zhai-v2  (KV 0a4c)
     │                        └── wrangler.v1.toml   → worker zhai     (KV ec1a)
     │
     └── deploy-web/server.js 直接 fs.readFile('../index.html') 注入令牌后吐页面
                │
                └── deploy-web/worker.mjs(= cat-chat-all-in-one.js 的副本,逐字节相同)
                          └── kv-sqlite.js(KV 接口的 SQLite 实现)

三处「双份/多份」是本次审查的重点来源:清单文件双份(index.html 与 bundle)、后端双份(worker.js 与 worker.mjs)、三套部署三份数据。

1.4 进程与变更时间线(按你我的聊天记录 + git 提交核准)

时间 事件
10-05 ~ 10-14 初版:Pages 前端 + worker zhai + KV chat_memory_v2(即现在的「v1」数据)
10-19 ~ 10-22 UI 重构、记忆系统(高光/经历/关系)、P0/P1 安全加固
10-23 22 点 v1 上批量「云端删除」68 条(当时只有软删,记录仍在库)
10-24 13 点 再删 211 条(同上,仍物理存在)
10-24 13:30 改为单文件构建(build.js),前端/后端分离成两个真源
10-24 晚 国内版 C 方案上线(server.js + SQLite)
10-24 深夜 按你要求新建全新环境:worker zhai-v2 + KV chat_memory_v3(空库)
10-25 06:34 ~ 09:01 全量档案补 mem:*、搬迁工具、606 条导入国内版(两库合并)
10-25 13:33 前端默认后端改同源(旧默认写死 v2 地址导致国内版把请求打穿墙)
10-25 13:52 桥接基准改用可信公网地址,修国内版 403
10-25 15:31 worker 输出页面按本部署令牌改写,修 v1 401
10-25 14:30 ~ 18:00 v1/v2 全量 500 排查 → 定位 KV 免费版每日 1000 次 list 配额
10-25 18:48 「彻底删除」改物理删除 + 墓碑防复活(先 v2、后 v1)
10-25 20:36 配额修复提交:list 前缀缓存 + 降级 + 干净 503
10-25 20:38 v2 部署 8c6fe98a
10-25 22:26 国内版进程被重启(node server.js 启动于 22:26,晚于 20:29 的代码更新)→ 已加载新代码(我此前说「国内版未重启」已过时;线上页面已确认含 data.notice 逻辑、注入 cat-web-token)
10-25 22:54 v1 部署 a3ef2a33(修复到你正在用的入口)

2 + 3. 问题清单与风险分级

分级标准:P0 致命(会导致数据错乱/丢失/越权,必须马上改)、P1 严重(功能不符合承诺或会踩坑)、P2 一般(边界、体验、配置)、P3 优化建议。 「位置」均为当前工作区文件的行号(行号会随修改漂移,请以函数名为准)。

P0(3 项)

P0-1 「Worker 地址」只对聊天生效,记忆/归档/删除永远打当前页面后端 → 一个设置就能把数据劈成两半

P0-2 「彻底删除」不彻底:只删 msg:/hl:/exp:/rel:,不删 mem:<profile>:* —— 而聊天检索读的正是 mem:

P0-3 任何人拿到三端任一 URL,即获得全部数据权限(页面公开 + 令牌随页面下发)

P1(7 项)

P1-1 v1 的模型 Key secret 名字不对,代码永远读不到;v2 完全没有模型 Key

P1-2 记忆检索只扫「最旧的 1000 个」key —— 超过 1000 条后新记忆永远检索不到

P1-3 国内版数据全在 /tmp,且同源基准写死 —— 容器重建即全丢;域名一变全部 POST 403

P1-4 备份/恢复能力三端不一致:v1/v2 只能「导出本机」,云端全量档案只有国内版能导入

P1-5 跨源响应缺少 Access-Control-Allow-Origin → 任何「页面在 A、后端在 B」的组合都会发送失败

P1-6 图片不入云:只有 [图片] 占位,图只存在发送它的那台浏览器

P1-7 首次「云端同步 / 恢复」是 O(n) 写入,1000 条就能一次打满免费版日配额

P2(13 项)

ID 问题 位置 影响
P2-1 部署产物与真源双轨:cat-chat-all-in-one.js 是生成物却直接部署;wrangler deploy --config 不会自动 build,忘了 build 就部署旧代码 wrangler*.toml main、deploy.sh 线上/仓库漂移(今晚 v1 能上修复纯因为我先 build 过)
P2-2 deploy-web/worker.mjs 是手工同步的副本(当前与 bundle 逐字节相同),无任何机制保证下次还一致 deploy-web/worker.mjs 国内版可能悄悄跑旧后端
P2-3 写入放大:每个 POST 先做 2 次限流写(rl_<ip> + rl_global),一次聊天约 6 写;限流写失败被 catch 吞掉 → 配额耗尽时限流静默失效 worker.js:1365-1399 日上限约 160 条聊天;配额耗尽后失去防刷
P2-4 ALLOW_CUSTOM_API="0" + 前端有「自定义」服务商:用户填非白名单域名必 403,提示却让去改 toml 两份 wrangler [vars];worker.js:1455-1463 自选模型名不副实
P2-5 validateApiUrl 返回 u.origin + u.pathname,丢掉 query worker.js:1465 带查询参数的接口(部分 Azure/代理网关)不可用
P2-6 主动消息只写 mem:,不写 msg: 归档 worker.js:2789-2792 /export/full 的 messages 缺主动消息;换设备恢复后聊天记录少几条
P2-7 三套导入导出接口并存:/backup(旧) / /archive(中) / /export/full(新),语义重叠 worker.js:1534/1648/1839 维护成本、口径混乱
P2-8 /init 是空实现;/health 版本号写死 2.0 worker.js:51-54, 1602-1609 三端无法区分「跑的是哪个版本」
P2-9 国内版回滚先 kv.clearAll() 再导入,导入失败即空库(靠 pre_rollback 快照兜底,而快照也在 /tmp) server.js:290-315 一次失败的回滚 = 全库清零
P2-10 平台 sharecode 写死在代码里;manifest 的图标与 start_url 不带 sharecode server.js:46、104-106;worker.js:1184-1206 国内版「添加到主屏幕」可能 403/白屏
P2-11 v2 的 AUTH_TOKEN 明文写在 index.html 第 10 行,已进 git 历史 index.html:10 令牌事实上公开
P2-12 /export/full、/archive 无速率限制(只校验令牌) worker.js:1611-1621 令牌一旦泄露可被刷爆配额
P2-13 worker.js 依赖构建期注入的 serveFrontend(源码里未定义);直接 deploy worker.js 会在 GET / 时 ReferenceError worker.js:1505 绕开 build 就炸

P3(11 项)

ID 问题 说明
P3-1 mem: 与 msg: 双写冗余 同一句话存两份,KV 键数与写入次数翻倍;也是 P0-2 的根因。中长期应合并为一份
P3-2 旧 mem_ 兼容读不走缓存 owner 每条聊天多 1 次 list(已刻意如此保证迁移可见,代价可控)
P3-3 /backup、/init 可废弃 前端未使用,属历史包袱
P3-4 ALLOWED_ORIGINS 填的是 worker 自己的 origin 无意义(同源本来就放行),建议留空或填真实跨源前端
P3-5 deploy-web/package.json 描述过时 写着「无 KV 记录,仅聊天 API 代理」,实际已有完整 KV
P3-6 worker.mjs 内嵌 300KB FRONTEND_B64 Node 端用不到(页面由 server.js 读文件),属无效载荷
P3-7 无 CI / 无 lint;测试未覆盖 CORS、令牌注入、WORKER_URL 覆盖 10 个测试文件全是「内存 Mock KV + Mock 模型」的契约测试,覆盖后端逻辑,不覆盖三端联调
P3-8 限流常量与真实配额未联动 RATE_DAY=400 与 KV 1000 写/天没有换算关系
P3-9 重试路径不处理 notice retryMessage 未读 data.notice,降级提示只在首次发送出现
P3-10 resolveTime 信任客户端 time.local 并直接拼进提示词 理论可注入提示词(仅自伤),建议清洗
P3-11 重试可能写入重复 mem 碎片;AI 归档时间戳复用请求时间 幂等只覆盖 msg:(worker.js:2586-2611),mem: 随机键无幂等;ai 记录用 time.ts(2702-2710),长回复时排序会抖

4. 修复方案(只给方案,不动手)

P0-1 统一「后端基准地址」

方案 A(推荐,一次性根治) 1. 前端新增 function apiBase():返回 CONFIG.WORKER_URL(去掉尾部 /)。 2. 把所有相对路径调用改为 apiBase() + '/xxx':/messages/delete、/archive、/export/full、/import/full、/highlights、/experiences、/relations、/proactive。 3. 设置页在「Worker 地址」变更时弹确认:「切换后端后,聊天、记忆库、云端存档将全部指向新地址(不同地址的数据互不可见)」。 4. 若判定「多后端」不是真实需求,更简单:删掉设置里的 Worker 地址输入,永远同源(三端各自自洽),从根上消除误配。

方案 B(最小改动):保留相对路径,但把设置页的 Worker 地址输入只读化/隐藏,并在页面加载时校验 CONFIG.WORKER_URL 与 location.origin 不一致时强制回退同源 + 提示。

验收:在 v2 页面把地址改成 v1 → 记忆库请求路径变为 v1(Network 面板可见),或按方案 B 直接被拒绝。

P0-2 让「彻底删除」真的彻底

方案 A(推荐,中期重构):把检索数据源统一到 msg:* 1. retrieveMemory 改为读 msg: 归档(已有 deleted_at 过滤),不再读 mem:*。 2. saveMemory 停止写入(或仅保留一段时间双写做回滚)。 3. 删除路径自然覆盖(msg: 删了就检索不到),不再需要扫 mem。 4. 迁移:把现有 mem:* 的内容按 content+ts 与 msg:* 对齐后清理(需 list,等配额恢复后分批做)。

方案 B(短期止血): 1. saveMemory 记录里增加 mid 字段(用户消息传 userMid,AI 消息传 aiMid)。 2. 硬删时:从最新往旧扫 mem: 键(最多 N 条),找到 mid 命中即 delete + 留墓碑;扫不到就记录告警。 3. 代价:每次删除多 1 次 list + 若干读写,且随库增长变慢。

验收:删一条消息后,/export/full 的 memories 与 messages 都不再包含该内容;再聊天时提示词里不出现。

P0-3 收敛访问权限(三选一,按你的使用场景定)

P1-1 修 Key

  1. v1:wrangler secret put API_KEY --config wrangler.v1.toml(值 = 你的 DeepSeek Key),或把代码改成 opts.apiKey || env.API_KEY || env.DEEPSEEK_API_KEY(更稳,兼容现状)。
  2. v2:同样加 API_KEY。
  3. 前端:当 CONFIG.API_KEY 为空时,在输入框上方提示「未填 Key,将使用服务端默认 Key(若已配置)」。

P1-2 修检索「只看得到最旧 1000 条」

方案(推荐):按月分片 1. 写入键改为 mem:<profile>:<YYYYMM>:<ts>_<rand>。 2. 读取时从当前月开始往前取最多 3 个月(每月 ≤200 键,天然分页),合并后按时间倒排取最近 300。 3. 兼容:旧 mem:<p>:<ts>_... 仍按原前缀读一次(只在没有新分片数据时)。 4. 备选:维护一个 mem_recent:<p> 索引键(存最近 300 个键名,写入时更新,读时一次 get)——写放大 +1,但读取 O(1)。

P1-3 国内版持久化与同源

  1. 数据目录改到平台持久卷(若有);若无,增加「每日把快照 base64 POST 到 v2 的 /import/full」做异地副本(注意配额,可每周一次)。
  2. PUBLIC_ORIGIN 改为:优先环境变量 → 其次读平台注入的公开域名 → 兜底现值;isSameOrigin 增加「同 host 的任意路径/端口」放行。
  3. 403 时返回中文可读页:「当前访问域名与后端配置不一致,请用 xxx 访问」,而不是让前端显示「发送失败」。

P1-4 统一备份口径

  1. 去掉 exportBackup 的 isWebMode() 限制:v1/v2 也调 /export/full(失败再退回本机导出)。
  2. 去掉 restoreCloudFullFile 的 isWebMode() 限制(/import/full 三端都支持)。
  3. schema:2 文件在三端行为一致。

P1-5 修 CORS

json(data, status, request, env) 增加后两个参数,内部 Object.assign(headers, corsHeaders(request, env));或在路由出口统一包一层。注意:corsHeaders 只在 Origin 合法时回 ACAO,避免变成开放代理。

P1-6 图片

短期:UI 明确标注「图片只保存在本机,换设备不会同步」;备份说明里写清。 长期:接 R2(或对象存储),msg.attachment 存对象 key,渲染时按需取;KV 只存元数据。

P1-7 分批同步

cloudSyncToServer 改为:本地记录 lastSyncTs → 只上传之后的;每批 50 条,逐批 POST,失败可续传。

P2 / P3 速查

ID 修法
P2-1 deploy.sh 已含 build;给 v1 也加一个 deploy-v1.sh(build → deploy --config),并把「禁止直接 wrangler deploy」写进 README
P2-2 构建时顺手 cp cat-chat-all-in-one.js deploy-web/worker.mjs(build.js 加一行),消除手工同步
P2-3 限流改成「一个键同时记分钟/天」(现在已是 1 键 2 写→可合并为 1 写);或把限流计数放模块内存(isolate 级,允许超发)以减少 KV 写
P2-4 要么 ALLOW_CUSTOM_API=1,要么前端「自定义」时校验域名并在 UI 提示白名单
P2-5 return u.origin + u.pathname + (u.search || '')
P2-6 handleProactive 里补 saveArchive(aiMid, ...),与聊天同款
P2-7 保留 /export/full + /import/full,把 /backup、/archive 标 deprecated,前端统一
P2-8 /health 返回构建时间戳(build.js 注入 BUILD_ID)
P2-9 回滚改为「导入到临时库 → 校验 counts → 原子替换文件」,失败不 clearAll
P2-10 manifest 动态生成时带上当前 query(sharecode);start_url 同样处理
P2-11 轮换 v2 token,页面改成占位符;Pages 若已废弃则直接删掉硬编码
P2-12 给 /export/full、/archive 加同款 checkRate
P2-13 build.js 里给 worker.js 加一行 function serveFrontend(){...} 的桩,或直接禁止部署 worker.js
P3-1~11 见上表,随主修复顺带处理

5. 建议的执行顺序(等你决定,我不会擅自动手)

第一批(止血,1 次部署,30 分钟内) 1. P1-5 CORS(1 处函数签名) 2. P1-1 Key 读取兼容(1 行)+ 两个 worker 加 secret 3. P2-5 query 丢失(1 行) 4. P2-13 serveFrontend 桩(防误部署炸)

第二批(数据正确性,需要设计确认) 5. P0-1 统一后端基准(决定「保留多后端」还是「锁死同源」) 6. P0-2 删除彻底化(推荐方案 A:检索改读 msg:) 7. P1-2 检索分片(与 6 一起改,避免两次动检索)

第三批(部署与数据面) 8. P1-3 国内版持久化 + 同源兜底 9. P1-4 备份口径统一;P1-6 图片策略;P1-7 分批同步 10. 历史 279 条软删数据的清理(硬删 + 墓碑,必须分批:每批 ≤200 条,否则写配额当天就满)

第四批(安全) 11. P0-3 访问控制(建议 Cloudflare Access,最快)

配额相关注意:KV 免费版 1000 list / 1000 write / 10 万 read 每天(账户级,北京时间早 8 点重置)。任何「批量清理 / 全量搬迁 / 首次同步」都要按 ≤200 条/批、跨天做,否则第二天照样把配额打满。


6. 本次审查的边界(做不到的部分,如实说明)

项 原因
无法枚举 v1/v2 的精确键数与总量 KV 免费版 list 配额已耗尽(code 10048),REST keys 端点同样被拒;只能靠已知样本与国内版导入结果推断(307 条归档来自 v1)
无法查看线上运行时日志 沙箱到 workers.dev 与 CF 日志端点均超时,wrangler tail 不可用
无法读取 secret 明文 CF API 不返回 secret 值;只能确认名称
无法在沙箱内直连 v1/v2 做端到端冒烟 workers.dev 被网络阻断;已改用「CF API 拉取线上 bundle + 逐字节/语义比对」替代(结果:v1 前后端修复代码与本地完全一致)
未验证真实设备上的表现 只读审查,未触发任何写操作

7. 一句话总结

三端代码现在是同一份修复版、都能跑;真正的风险不在「能不能用」,而在架构层面的三处不一致:①前端一半接口认「设置的地址」、一半认「当前页面」(P0-1);②消息存两份、删除只删一份(P0-2);③三端各有一份互不同步的数据,图片只在本机(P1-3/4/6)。这三件事不解决,后面每一次「修好了」都会在换端/换设备/换域名时重新裂开。


全项目代码基线审查(2026-10-23)

小手机项目 · 全项目代码基线审查报告(只读版)

审查对象:beibbloveme-crypto/yuai(私有仓库,未认证 GitHub API 返回 404 证实非公开) 审查基线:分支 fix/p0-memory-consistency,HEAD = f02cc07(2026-10-22 19:19:35 +0800) 生产版本:51ac31d3-98db-4740-8b8e-f5de09fd7d43(2026-10-22 19:19:43 +0800,经 wrangler API 只读核验,与 HEAD 内容吻合) 审查性质:只读。未修改任何代码、未提交、未部署、未触碰线上数据。所有测试均在本地内存 Mock KV + Mock 模型 + 虚构令牌下运行。 状态标注体系:【已验证完成】【部分完成】【未实现】【存在缺陷】【无法确认】


一、项目基线总览

1.1 仓库与分支状态

项 值 证据
远端 github.com/beibbloveme-crypto/yuai(私有) git remote -v;未认证 api.github.com 返回 404
当前分支 fix/p0-memory-consistency git branch
HEAD f02cc07「fix(ui): 去掉自加的抽搐源」 git log -1
main 分支 HEAD 75657e0(阶段4 文档提交) git log main -1
本地/远端一致性 一致(fetch 后无差异) 前期 git fetch 核验

【存在缺陷】分支割裂:P0/P1 六个修复提交(9505597 记忆隔离、513f24b 稳定 mid、d4aa34e 删除接口、2460f9a 全量导出导入、5bfe8c8 上下文组装、5b01441 构建修复)只存在于 fix 分支,main 分支不包含(git merge-base --is-ancestor 9505597 main 判定为否)。生产环境实际运行 fix 分支代码,main 已落后生产 15 个以上提交。任何从 main 发起的部署都会丢失全部记忆一致性修复。

1.2 文件清单与职责

文件 大小 职责 性质
index.html 208 KB(约 11560 行) 前端唯一真源 源文件
worker.js 138 KB(2510 行) 后端唯一真源 源文件
cat-chat-all-in-one.js 417 KB 部署产物 = worker.js + base64 内联前端 + serveFrontend() 构建产物
build.js 2 KB 由两个真源重新生成部署产物 构建脚本
deploy.sh 808 B node build.js → node --check → npx wrangler deploy 部署脚本
wrangler.toml 1.4 KB Worker 配置 配置
tests/(7 个 .mjs) — P0/P1/阶段4 回归测试 测试
9 个 .md 文档 + 12 张截图 — 交接/安全/审计文档 文档

构建漂移核验【已验证完成】:在 /tmp/buildcheck 用仓库内的 build.js 对当前 worker.js + index.html 重新构建,产物与仓库中的 cat-chat-all-in-one.js 逐字节一致(diff 无输出)。不存在"只换前端、后端漂移"问题。

1.3 部署配置(wrangler.toml)

name = "zhai"
account_id = "d285d56ff8e27fbe825c552b7a86cc65"
compatibility_date = "2024-09-23"
KV binding: CHAT → ec1a5feca1f446a28c6bb12b5ed7accc
vars: API_URL=https://api.openai.com/v1/chat/completions
      MODEL=gpt-4o-mini
      ALLOWED_API_HOSTS=""
      ALLOW_CUSTOM_API="0"
      ALLOWED_ORIGINS=""

线上 secrets(wrangler secret list 只读查询):仅 DEEPSEEK_API_KEY 一项。

【存在缺陷】死配置:callModel(worker.js L851)读取的是 env.API_KEY,全仓库无任何代码引用 DEEPSEEK_API_KEY(grep 证实)。即:线上配置的模型密钥永远不会被使用,实际调用模型完全依赖前端请求体里传来的 apiKey;未传时返回 missing_api_key 400。文档"API_KEY 不强制:前端自选填入即可"(L29 注释)与代码行为一致,但 secret 命名与代码读取名不一致属于配置事故。

无 AUTH_TOKEN secret:getToken(L1193-1195)回退到源码内硬编码的 DEFAULT_AUTH_TOKEN = '518effb141664b32940bf691cf65e506'(L1100)。详见第七部分安全分析。

1.4 文档与代码差异比对

文档 声明 实际 判定
安全说明.md 生产版本 93c1d1d8-a503-47db-9625-8a0333374034 实际 51ac31d3-98db-4740-8b8e-f5de09fd7d43 【文档过期】
任务交接说明.md 当前生产版本 93c1d1d8(2026-10-17 安全加固 v2) 同上,已被 10-22 共 9 个 UI 提交超越 【文档过期】
修复记录-P0-P1.md "未部署生产;线上仍为 f490d9d2" P0/P1 修复已在生产(fix 分支即生产内容) 【文档过期】
安全说明.md "源码里只有占位符 __CAT_TOKEN_VALUE__,不落盘" 源码 L1100 有硬编码 DEFAULT_AUTH_TOKEN,占位符仅在页面注入时被替换(L1361) 【文档与代码不符】
安全说明.md 各项限额(1MB/4000 字/800KB 图/10 条历史/500 tokens/25s 超时/20·400·1500 限流) 与 SEC 常量(L1080-1094)逐条一致且均有强制点 【已验证完成】
安全说明.md 出站白名单、禁 IP、禁重定向、错误脱敏 L1262-1327、L881-903 逐条证实 【已验证完成】

二、前端审查(index.html)

2.1 结构总览

2.2 功能完成度

功能 状态 证据
聊天收发(文本 + 图片 inline base64 + 附件元数据) 【已验证完成】 消息渲染链路 + P0-2 测试 7.1-7.3 通过
表情包 / kaomoji 面板、点击直发 + 发送前压缩 【已验证完成】 stickerWrap/kaomojiGrid 渲染点 L9531/L9064
收藏(微信式 UI、双向、显示收藏者) 【已验证完成】 favWrap L9333;favoriteMessage → addCloudHighlight
记忆库面板(高光/经历/关联三 tab + 删除 + 高光关键词搜索) 【已验证完成】 memoryModal L8646+;/highlights /experiences /relations 调用点
云同步(导出到云端 / 从云端恢复,幂等) 【已验证完成】 cloudRestoreBtn L8640;/archive 调用
删除消息(先服务端后本地,防复活) 【已验证完成】 P0-3 测试 23/23 通过
设置持久化(人设/Key/地址/模型/头像/背景) 【已验证完成】 L10546-10723 storage 读写
全屏(Fullscreen API + PWA manifest + 添加主屏幕引导) 【已验证完成】 23fd149 提交;manifest/icon 路由 L1431-1442
图片灯箱 / showToast 【已验证完成】 任务交接说明批次 + 代码存在
iOS Safari 全屏初始化 + 键盘跟随(本轮 9 个 UI 提交) 【已验证完成】 HEAD 即最终版;线上已部署
流式回复(SSE/stream) 【未实现】 全文 0 处 stream:true/text/event-stream/ReadableStream
全局消息搜索 【未实现】 仅记忆库内高光搜索(L4253 placeholder"搜索高光原话…")
多角色 / profile 切换 UI 【未实现】 前端无任何 profile 切换控件;后端 profile 机制存在但前端只用默认 owner
深色模式 【死功能】 72 处 .dark-mode CSS,0 处 JS 添加/切换该类(grep classList/toggle/className 无命中)
顶部栏颜色设置 【存在缺陷】 设置项存在但无生效代码路径(审查确认无效)
相对路径 / WORKER_URL 割裂 【存在缺陷】 file:// 直接打开 index.html 时 WORKER_URL 落占位符 https://YOUR-WORKER-SUBDOMAIN.workers.dev,全部调用失败;必须经 Worker 托管或手动在设置里填地址

2.3 XSS 面


三、后端 API 审查(worker.js)

3.1 路由全表(fetch handler L1415-2238)

# 路由 方法 鉴权 限流 说明
1 /manifest.webmanifest、/apple-touch-icon.png、/icon-192.png、/icon-512.png GET 公开 无 静态资源,不含数据
2 /health GET 公开 无 仅 ok/service/version/time
3 /init POST 令牌(L1455) 无 ensureSchema
4 /backup GET 令牌(L1458) 无 备份导出
5 /archive GET/POST 令牌(L1492 专属 checkAuth) 无 GET=档案导出(过滤软删);POST=幂等导入
6 /export/full GET 令牌 无 全量导出 schema 2
7 /import/full POST 令牌 无 全量导入,8MB 上限(L1726),dry_run
8 /proactive POST/GET 令牌(L1457) 有(L2096) 主动消息
9 /messages/delete POST 令牌(POST 即保护) 无 软删/硬删 + 引用检查 + 级联
10 /highlights /experiences /relations GET/POST/DELETE 令牌(L2017) 无 记忆库三实体
11 任意其他路径 POST 令牌(L1455) 有(L2179) 聊天主接口 handleChat
12 任意其他路径 GET 公开 无 返回前端页面(运行时注入令牌 L1361)

鉴权覆盖面结论【已验证完成】:所有数据读写路径均有令牌保护;不存在未授权读数据的 GET 路由。鉴权链:isSameOrigin(L1168,浏览器带异源 Origin 直接 403)→ protectedRoute 判定 → checkAuth → safeEqual 定长比较(L1198,防时序侧信道)。

3.2 出站安全(callModel L847-923)

机制 状态 证据
https 强制 / 禁账号密码 / 禁非 443 端口 【已验证完成】 L1300-1308
主机白名单(8 家内置 + ALLOWED_API_HOSTS 追加) 【已验证完成】 L1103-1112、L1316-1324
禁 IP 字面量(点分十进制 + IPv6 括号/冒号) 【部分完成】 L1284-1285 正则仅挡 ^\d{1,3}(\.\d{1,3}){3}$ 与含 :/[ 形式
禁 localhost/.local/.internal/metadata.google.internal 【已验证完成】 L1275-1281
redirect:'manual' + 3xx 一律拒绝 【已验证完成】 L881、L891-893
上游错误脱敏后入日志,不回传前端 【已验证完成】 L896-903 sanitizeErrorText
模型输出 max_tokens=500、超时 25s AbortController 【已验证完成】 L862-863、L878、L1090

【存在缺陷】SSRF 整数 IP 穿透:L1284 只挡点分十进制。以下形式可绕过:十进制整数 IP(https://2852039166/ = 169.254.169.254)、十六进制 IP(https://0xa9fea9fe/)、解析到内网的公网域名(如 localtest.me、nip.io 类)。Cloudflare 出口本身不完全等价于目标网络位置,且白名单机制(默认 ALLOW_CUSTOM_API=0)已挡住绝大多数场景,故定为中危而非高危——但若运维设置 ALLOW_CUSTOM_API=1,该缺陷升级为高危。

3.3 限流(checkRate L1249 / bumpCounter L1226)

3.4 检索与上下文(tokenize L620 / retrieveMemory L641 / retrieveContext L695 / buildSystemPrompt L716)

3.5 写入路径

3.6 主动消息(handleProactive L2444-2510)


四、数据存储与同步审查(Cloudflare KV)

4.1 键空间设计

前缀 内容 隔离方式
msg:<profile>:<mid> 消息(含 deleted_at、message_type、附件元数据、sync_status) profile 前缀
mem:<profile>:<id> 原话记忆 profile 前缀
hl:<profile>:<hid> 高光原话 profile 前缀
exp:<profile>:<eid> 经历记忆(active/candidate) profile 前缀
rel:<profile>:<rid> 关联关系 profile 前缀
proactive:<profile>:* 主动消息计数 profile 前缀
rl_<hash> / rl_global 限流计数器(TTL 172800) IP 哈希
meta_lastTs:<profile> + 遗留 meta_lastTs 最后聊天时间 profile
mem_*(遗留) 旧版记忆键 仅 owner 只读兼容(L595-602,MEM_READ_LEGACY=1 默认开)

4.2 同步机制

机制 状态 证据
/archive GET 导出(过滤软删、支持关键词/时间/说话方/分页) 【已验证完成】 P1-7 测试 14/14
/archive POST 导入(幂等按 mid 去重) 【已验证完成】 P0-3 测试 6.1/6.2(重复同步 imported=0 skipped=2)
/export/full + /import/full(schema 2、dry_run、保留 deleted_at、图片附件元数据、8MB 上限、逐条错误报告) 【已验证完成】 P0-4 测试 27/27
删除同步(软删不复活、引用检查、级联软删) 【已验证完成】 P0-3 23/23
跨 profile 隔离(导出/导入/删除/记忆) 【已验证完成】 P0-1 15/15、P0-4 7.1/7.2
事务性 / 原子性 【未实现】 KV 无多键事务;导入部分失败留半成品(逐条 catch 不整体回滚)
版本管理 / 冲突解决 【未实现】 无版本号、无冲突检测,last-write-wins
断点续传 / 增量同步 【未实现】 仅全量 + 幂等跳过

4.3 测试结果汇总(本阶段实际运行)

测试文件 结果
test_p0_memory_isolation.mjs 15/15 通过
test_p0_mid_refs.mjs 21/21 通过
test_p0_delete_sync.mjs 23/23 通过
test_p0_export_import.mjs 27/27 通过
test_p1_archive_search.mjs 14/14 通过
test_p1_memory_context.mjs 13/13 通过
test_phase4_v2.mjs 32/32 通过
合计 145 断言通过,0 失败

测试均为内存 Mock KV + Mock 模型 + 虚构数据/令牌,未触碰线上。


五、记忆系统专项

5.1 现状机制

用户消息 ──► handleChat ──► saveMemory(L129) ──► mem:<profile>:<id>(原话,cleanText 4000)
                    │
                    ├─► retrieveContext(L695) ──► memories(≤8) + highlights(≤3) + experiences(≤3) + candidates(≤2)
                    │                                │
                    │                                ▼
                    └──────────────────────────► buildSystemPrompt(L716) ──► callModel

5.2 记忆系统的能力缺口(相对工业级记忆系统)

能力 现状 状态
记忆变更决策(新增/更新/删除/无操作) 只有"追加写",无对旧记忆的 UPDATE/DELETE 决策,矛盾记忆并存 【未实现】
记忆版本链 / 变更历史 无 history 表,记忆被覆盖即丢失前值 【未实现】
双时间戳(生效/失效/过期) 仅单时间戳 created_at;经历有 time_certainty 字段但非真正的时间推理 【部分完成】
混合检索(关键词 + 语义) 纯关键词子串 【未实现】
上下文 token 预算控制 按条数硬截(8/3/3/2),无 token 计量 【部分完成】
人工审核队列 无;candidate 仅是状态字段,无审核界面/流程 【未实现】
记忆容量管理 300 条扫描上限即事实容量上限,无归档/分层/淘汰 【存在缺陷】
任务恢复 / 持久状态 无 【未实现】

六、原有功能保留清单

以下为对"已有功能逐项核对"的结果(用于后续重构时的保留基准):

# 功能 位置 状态
1 聊天收发(文本/图片/重试复用 client_mid 幂等) 前端消息链路 + worker handleChat 【已验证完成】P0-2
2 图片消息(inline base64 + kind/mime/bytes 元数据保留) L2271 MAX_IMAGE 校验 + 测试 7.1-7.3 【已验证完成】
3 表情包 / kaomoji(点击直发 + 压缩) stickerWrap/kaomojiGrid 【已验证完成】
4 收藏(双向 + 云端高光联动) favWrap + /highlights 【已验证完成】
5 记忆库面板(高光/经历/关联 + 删除 + 高光搜索) L8644-8740 【已验证完成】
6 云同步(/archive 导出导入 + /export/full + /import/full) L1491-2012 【已验证完成】P0-3/P0-4
7 删除(软删/硬删/引用检查/级联/防复活) L2121-2169 【已验证完成】P0-3
8 主动消息(每日 3 次 + 人设 + 记忆找话题) L2444-2510 【部分完成】功能可用,但时间可伪造
9 多 profile 隔离(后端) L171-172、L591-613 【已验证完成】P0-1;前端无切换 UI
10 旧数据兼容(mem_* / meta_lastTs 遗留键 owner 只读) L595-602、L996-1000 【已验证完成】P0-1 3.1-3.3
11 限流(IP 分/日 + 全局日) L1249-1260 【部分完成】fail-open + KV 一致性
12 出站白名单/SSRF 防护 L1262-1327 【部分完成】整数 IP 穿透
13 令牌鉴权 + Origin 同源校验 L1168-1200 【部分完成】默认令牌硬编码(见七)
14 全屏/PWA/灯箱/toast/添加主屏幕 23fd149 批次 【已验证完成】
15 iOS 键盘/视口适配 syncViewportMetrics L10909-11035 【已验证完成】(本轮 9 提交,线上即此版)
16 深色模式 72 处 CSS 【死功能】无切换代码
17 流式回复 — 【未实现】
18 全局消息搜索 — 【未实现】(仅记忆库内高光搜索)
19 顶部栏颜色设置 设置项 【存在缺陷】无效

七、安全、稳定性与部署

7.1 生产与开发环境区别

7.2 高危问题(3 项)

# 问题 证据 缓解与残余风险
H1 硬编码默认访问令牌:DEFAULT_AUTH_TOKEN='518effb…'(L1100)随源码与构建产物分发;线上未配置 AUTH_TOKEN secret(secret list 仅 DEEPSEEK_API_KEY)。isSameOrigin 对无 Origin 的直连请求放行(L1170"非浏览器调用没有 Origin,由令牌把关")——持令牌者可用 curl 直接调所有接口 L1100、L1193-1195、L1168-1170、wrangler secret list 缓解:仓库私有(未认证 API 404)。残余:任何获得仓库/构建产物读取权限的人即持令牌;令牌无轮换机制(注释说轮换需 wrangler secret put AUTH_TOKEN,但从未执行过)
H2 主动消息客户端时间伪造:resolveTime 信任 t.iso,day_key 客户端可控 → 绕过 3 次/日配额、污染时间线 L84-105、L2447、L1009 缓解:需令牌 + 限流。残余:配额与时间线完整性对持令牌者不设防
H3 限流 fail-open + KV 最终一致性:计数器读写失败均吞异常,读失败从 0 起算 L1226-1247 缓解:全局配额仍有大部分时间有效。残余:KV 抖动期间可无限刷

7.3 中危问题(6 项)

# 问题 证据
M1 /archive POST 导入无体积上限,大 body 可 OOM/超时 L1613-1616(对比 L1726 的 8MB 检查缺失)
M2 SSRF 整数/十六进制 IP 与解析到内网的域名可绕过 IP 字面量检查 L1284-1285
M3 KV 读取放大:单次 /chat 约 300+ 次 KV 读,直接推高成本与延迟 L641-651、L695-702
M4 硬删消息不清 mem: 记忆碎片,永久残留且参与检索 L2157-2158、cascadeDeleteRefs L499
M5 meta_lastTs 双写 + 读-改-写竞态 L155-158
M6 线上 secret DEEPSEEK_API_KEY 与代码读取的 env.API_KEY 不匹配(死配置,实际模型 Key 全靠前端传入,服务端无兜底 Key) L851、secret list

7.4 低危与其他(5 项)

# 问题 证据
L1 无 CSP / HSTS 等安全响应头(仅 referrer-policy) L1370
L2 API Key 明文存前端 localStorage(model_api_key) L5073
L3 main 分支落后生产 15+ 提交,从 main 部署将丢失全部 P0/P1 修复 git merge-base --is-ancestor 核验
L4 三份文档的生产版本号过期(93c1d1d8 / f490d9d2) 第一·1.4
L5 /proactive 与 /messages/delete 等写路径无限流(仅 /chat 与 /proactive 有) L2096 vs L2121

7.5 测试覆盖缺口

已覆盖:记忆隔离、mid 一致性、删除同步、导出导入、档案搜索、上下文组装、阶段4 回归(145 断言)。 未覆盖:限流触发与 fail-open、SSRF 各绕过向量、时间伪造、并发竞态、体积上限、令牌轮换、前端渲染回归(无前端测试框架)。


八、开源项目对照研究(Mem0 / Graphiti / Letta / LangGraph)

研究方法:基于四个项目公开仓库的源码结构(非 README 宣传)分析其记忆/时间线/关系/持久状态/任务恢复/人工审核机制。结论:不照搬任何框架,仅提炼可借鉴机制与 KV 架构下的低成本落地方式。

8.1 Mem0——记忆变更决策与版本链

8.2 Graphiti——双时间戳与时序知识图谱

8.3 Letta——分层记忆与上下文预算

8.4 LangGraph——持久状态、任务恢复与命名空间

8.5 横向对比与 KV 架构设计建议

维度 Mem0 Graphiti Letta LangGraph 当前项目
记忆变更 四态决策 + history 边失效 Block 覆盖 状态覆盖 仅追加
时间建模 created_at valid/invalid/expired 三戳 无 时间戳 单 created_at
关系 隐式 显式图谱 + RRF 无 无 rel 实体(轻量)
检索 BM25 加性 RRF 混合 分层预算 — 关键词子串
上下文控制 — — token 预算 — 条数硬截
任务恢复 — — — checkpointer 无
人工审核 依赖外部 依赖外部 依赖外部 依赖外部 无

给后续记忆系统重新设计的 5 条建议(均基于现有 KV 架构,零新基础设施):

  1. 确定性规则 + history 版本链(借鉴 Mem0):写入前用确定性规则(同实体高相似度 → UPDATE 并写 memhist: 链)替代无条件追加;LLM 四态决策作为可选增强,默认关闭以控成本。
  2. 双时间戳(借鉴 Graphiti):给 exp:/rel: 记录加 valid_at/invalid_at 字段,检索时过滤已失效项——这是修复"矛盾记忆并存"的最低成本手段。
  3. RRF 融合预留(借鉴 Graphiti):retrieveMemory 的排序层抽象成"多路候选 → 融合"接口,当前只有关键词一路;未来接入任何语义检索时直接挂载。
  4. DO + WAL 流水线(借鉴 LangGraph checkpointer 思想,规避 KV 无事务):把"一次聊天的多次 KV 写"先写 Durable Object 的 WAL 再异步落 KV,解决 7.2-H3 之外的写入原子性与读取放大(M3)。
  5. core memory + 上下文预算(借鉴 Letta):prompt 组装从"条数硬截"改为"core 必进 + archival 按 token 预算填充",token 估算用 bytes/4 近似。

引入成本与风险总评:以上 5 条均可在现有 Worker + KV 上实现,不引入新服务;主要成本是 LLM 决策调用(可选)与一次检索层重构。明确不建议直接引入 Mem0/Graphiti/Letta/LangGraph 框架本体——它们分别自带向量库/图数据库/服务端运行时,与当前"单 Worker + KV"的极简架构和零外部依赖原则冲突。


九、升级前置条件(重新设计记忆系统之前必须解决)

按优先级排序,每条对应前文证据:

  1. 分支合并:将 fix/p0-memory-consistency 合回 main 并重新确立基线,否则任何"从 main 重构"都会丢失 P0/P1 全部修复(7.4-L3)。
  2. 令牌治理:部署 AUTH_TOKEN secret 替换硬编码默认值;建立轮换流程;评估"共享静态令牌"是否足够(当前页面注入方案决定了令牌必然随页面下发,跨站已被 Origin 校验挡住,主要风险是源码/产物扩散)(7.2-H1)。
  3. 时间服务端化:resolveTime 忽略客户端 iso 或取 max(server_now, client);主动消息配额改按服务端日期(7.2-H2)。
  4. 限流加固:计数器失败时 fail-closed 或至少告警;评估 Durable Object 计数器的成本(7.2-H3)。
  5. /archive 导入加体积上限(对齐 /import/full 的 8MB 或更小)(7.3-M1)。
  6. SSRF 规范化:IP 字面量检查前做规范化(解析十进制/十六进制/短形式为点分十进制再判)(7.3-M2)。
  7. KV 成本治理:单次 chat 300+ 读的放大必须先行解决(DO 缓存 / 批量读 / 降低 SCAN_LIMIT 并配归档),否则新记忆系统只会更重(7.3-M3)。
  8. 硬删补清理:硬删消息时级联清理 mem: 碎片,或在检索层按来源 mid 过滤已删消息(7.3-M4)。
  9. secret 命名对齐:把线上 DEEPSEEK_API_KEY 改为代码读取的 API_KEY,或改代码读 secret 现名;确定服务端是否保留兜底 Key(7.3-M6)。
  10. 文档版本机制:三份文档的生产版本号与实际部署脱节,建立"部署后回写版本号"的流程(7.4-L4)。
  11. 测试补齐:为限流、SSRF、时间伪造、并发、体积上限补测试;当前 145 断言不覆盖上述任何一项(7.5)。
  12. 记忆容量方案:300 条扫描上限是事实容量天花板,重构前需确定归档/分层/淘汰策略(5.2)。

十、证据索引

10.1 代码位置索引(worker.js,共 2510 行)

机制 行号
SEC 常量(限额/限流/超时) L1078-1096
DEFAULT_AUTH_TOKEN(硬编码令牌) L1100
DEFAULT_API_HOSTS(出站白名单) L1103-1112
PROACTIVE_MAX_PER_DAY=3 / DEFAULT_PROFILE / VALID_PROFILE L39 / L171 / L172
SCAN_LIMIT=300 L44
saveMemory L129
meta_lastTs 双写 L155-158
cascadeDeleteRefs L499
listMemoryKeys(含旧键兼容) L591-613
tokenize L620-639
retrieveMemory L641-692
retrieveContext L695-709
buildSystemPrompt L716-840
callModel(超时/禁重定向/脱敏) L847-923
resolveTime(客户端时间信任点) L84-105
proactiveState / bumpProactive L980 / L1014
logEvent L1146-1150
corsHeaders L1153-1165
isSameOrigin L1168-1190
getToken / checkAuth / safeEqual L1193-1195 / L1198+
hashShort / bumpCounter / checkRate L1215-1260
isForbiddenHost / validateApiUrl L1271-1327
页面响应 + 令牌注入(TOKEN_MARK 替换) L1361
fetch handler(路由/鉴权/兜底) L1415-2238
handleProactive(时间伪造入口) L2444-2510
/archive POST 导入(无体积限制) L1613-1672
/import/full(8MB 上限) L1679-2012
/messages/delete L2121-2169
聊天主接口(限流 + 1MB + 兜底) L2171-2230

10.2 代码位置索引(index.html,约 11560 行)

机制 行号
令牌占位符注入点 L7
.dark-mode CSS(72 处,无 JS 切换) L80 起
storage 键表(13 个 localStorage 键) L5053-5083
CONFIG / WORKER_URL 默认值 L5125-5140
消息输入框 L4027-4032
记忆库面板(含 escapeHtml 渲染) L8644-8740
escapeHtml L8667
X-Cat-Token 请求头(7 处) L7058、L7994、L8495、L8532、L8677 等
syncViewportMetrics(视口/键盘适配终版) L10909-11035

10.3 配置与部署证据

10.4 测试证据(本阶段实际运行,全部通过)

test_p0_memory_isolation.mjs   15/15   profile 隔离 + 旧键兼容
test_p0_mid_refs.mjs           21/21   mid 一致性 + 引用校验 + 图片元数据
test_p0_delete_sync.mjs        23/23   软删/硬删/级联/防复活/隔离
test_p0_export_import.mjs      27/27   全量导出导入/幂等/dry_run/软删保留
test_p1_archive_search.mjs     14/14   关键词/时间/说话方/分页/软删过滤
test_p1_memory_context.mjs     13/13   记忆进 prompt/候选分离/条数上限
test_phase4_v2.mjs             32/32   阶段4 原功能回归
合计                           145/145 通过,0 失败(Mock KV + Mock 模型 + 虚构令牌)

10.5 开源项目参考

10.6 未能核验事项(【无法确认】)

  1. 线上 /health 实际响应:沙箱网络无法访问 zhai.beibbloveme.workers.dev(curl 超时),版本一致性结论基于 wrangler API 元数据而非线上抓取。
  2. 限流在真实 KV 上的实际拦截效果(需构造超限流量,未执行)。
  3. KV 中现存数据量(未做线上 list 全量扫描,避免无谓的读放大与费用)。
  4. 仓库确切的协作者/权限名单(GitHub API 未认证仅确认私有)。

附:审查结论摘要

本阶段任务至此结束。按任务书要求,交付本报告后停止,不开始重构,不开发新记忆系统,不修改任何既有代码与线上配置。


完整交付文档(2026-10-22)

小手机(猫爪聊天)· 完整交付文档

一份文档看懂整个项目:源码结构 + 功能介绍 + 开发进程 + 测试证据 + 交接信息

生成时间:2026-10-22 | 代码基线:分支 fix/p0-memory-consistency @ b03a612(main @ 75657e0) 线上版本:f490d9d2-8ee5-4d0c-9274-b082b5d34990(2026-10-19T07:14:23Z 起 100% 在线,早于本轮修复,未包含 P0–P1 改动)

证据约定:本文所有结论分三级——【源码】= 直接读代码得出;【实测】= 本地跑测试得出;【未验证】= 沙箱无法证实(如线上字节一致性、GitHub 远端实时状态)。凡未标注者均为【源码】级。


目录

  1. 这份文档怎么用
  2. 项目概况
  3. 源码介绍(逐文件 · 逐模块 · 逐接口)
  4. 功能介绍(完整清单)
  5. 架构与数据流
  6. 开发进程记录(从 0 到当前)
  7. 测试与证据
  8. 交接信息(仓库 / 备份 / 部署 / 敏感项)
  9. 已知限制与技术债
  10. 待办与后续阶段
  11. 常用操作手册 附录 A · 证据索引 | 附录 B · 术语表

0. 这份文档怎么用

你是谁 建议读法
接手审查的 AI / 开发者 第 1、2、4 章建立全局 → 第 5 章理解演进 → 第 6、7 章核对证据 → 第 8、9 章看风险
只想改 UI 第 2.5 节前端地图 + 仓库内《模块索引.md》(按区域点单,改完 node build.js 重新内联)
只想改后端 第 2.2 节后端地图 + 第 2.3 节路由表 + 第 2.4 节 KV 键位图
只关心安不安全 第 3.6 节安全层 + 第 8 节已知限制 + 仓库内《安全说明.md》
只想复现测试 第 6 章测试矩阵 + 第 10 章命令

本文不包含:任何真实密钥、令牌、wrangler.toml 中的账号 ID 与 KV ID(这些只在本机工作区)。文中出现的 ID 均为仓库文件内已有的非机密标识。


1. 项目概况

1.1 一句话

一个单文件 Cloudflare Worker 全栈应用:前端是一个仿微信的高定制聊天界面(唯一真源 index.html,11241 行),后端是同一仓库的 worker.js(2510 行),构建时内联成单一部署文件 cat-chat-all-in-one.js,数据全部存 Cloudflare KV,大模型 Key 由用户在前端自行填写、经 Worker 代理调用。

1.2 基本信息

项 值 证据
产品名 猫爪聊天(交接代号:小手机) 源码注释
线上地址 https://zhai.beibbloveme.workers.dev 【未验证】沙箱访问 workers.dev 返回 000,无法确认当前在线字节
部署平台 Cloudflare Workers(name = "zhai") 【源码】wrangler.toml
数据存储 Cloudflare KV(binding CHAT)+ 浏览器 localStorage/IndexedDB 【源码】
前端 原生 HTML/CSS/JS 单页,无框架、无构建依赖 【源码】
后端 Worker fetch 处理器,无框架 【源码】
模型 OpenAI 兼容接口,多服务商可选(OpenAI / DeepSeek / 通义 / 自定义) 【源码】
Git 仓库 https://github.com/beibbloveme-crypto/yuai.git 【源码】git remote -v
当前分支 fix/p0-memory-consistency(领先 main 7 个提交,未推送) 【实测】git status
提交总数 20(基线 1 + UI/人设 5 + 阶段 0–4 五轮 + P0–P1 修复 7 + 文档 2) 【实测】git log

1.3 目录结构

/workspace/chat/
├── index.html                  # 前端唯一真源(11241 行 / 196KB)
├── worker.js                   # 后端唯一真源(2510 行 / 138KB)
├── build.js                    # 构建脚本(58 行):worker.js + index.html → 部署文件
├── cat-chat-all-in-one.js      # 部署产物(2531 行 / 401KB)= 后端 + FRONTEND_B64 + serveFrontend()
├── wrangler.toml               # Worker 配置(含真实账号/KV ID,勿外传)
├── deploy.sh                   # 备用部署脚本(CF_API_TOKEN 方式)
├── icons/                      # PWA 图标(192/512/apple-touch)+ gen_icon.py 生成脚本
├── tests/                      # 7 个测试脚本(Node 直调 + 内存 Mock KV)
├── 任务交接说明.md              # 交接总览
├── 安全说明.md                  # 安全层设计
├── 审计报告-阶段0.md            # 阶段 0 只读审计
├── 修复记录-P0-P1.md            # 本轮修复的提交级记录
├── 模块索引.md                  # 改代码前的区域地图(按区点单)
├── 猫爪聊天-完整项目.md          # 历史融合版全代码(占位符版,可外传)
├── 替换指南.md / 给其他AI-代码包/  # 历史交接材料
├── 效果图-*.png / 新界面-*.png   # 17 张界面截图(其中 2 张未提交)
└── .wrangler/                  # 本地 wrangler 状态(已 gitignore,可能含登录凭证)

2. 源码介绍

2.1 三个核心文件的分工(先理解这个,再谈任何改动)

index.html ──┐
             ├──► build.js ──► cat-chat-all-in-one.js ──► wrangler deploy ──► 线上
worker.js ───┘                   (部署文件)
文件 角色 纪律
index.html 前端唯一真源。改 UI 只改这里 改完必须 node build.js 重新内联,否则线上不生效
worker.js 后端唯一真源。改后端只改这里 同上;新函数放在 export default { 之前(顶层)
cat-chat-all-in-one.js 构建产物,不要手工编辑 由 build.js 整体重写;部署前校验与 worker.js 前缀逐字节一致

历史上踩过的坑(构建漂移):早期 build.js 只替换前端 base64、不同步后端,导致部署文件的后端长期停在旧版——线上跑的后端和仓库里的 worker.js 不是同一份代码。本轮已重写 build.js 为整体生成,并以 SHA256 校验前缀一致(c137a6ca…)。【实测】

build.js 核心逻辑(全文 58 行,摘录):

const b64 = Buffer.from(html, 'utf8').toString('base64');
const backend = fs.readFileSync(workerPath, 'utf8');
const serveBlock = `... const FRONTEND_B64 = "${b64}"; function serveFrontend(){...} ...`;
fs.writeFileSync(outPath, backend.replace(/\s+$/, '') + '\n' + serveBlock);

2.2 后端源码地图(worker.js,行号为当前 HEAD)

区域总表

区域 职责 关键函数(行号)
工具层 JSON 响应、时间键、时段解析 json() 66 · dayKey() 77 · resolveTime() 84 · periodOf() 114
P 记忆系统 统一 profile / 结构化档案 / 高光 / 经历 / 关系 saveMemory() 129 · profileOf() 176 · msgKey() 182 · saveArchive() 187 · listArchiveKeys() 211 · listLegacyMemKeys() 230 · hlKey/expKey/relKey() 269-271 · listByPrefix() 273 · collectAllByPrefix() 289
高光 原话高光库 CRUD listHighlights() 299 · addHighlight() 319 · deleteHighlight() 359
经历 经历记忆 CRUD(active/candidate) listExperiences() 369 · addExperience() 389 · deleteExperience() 430
关系 高光↔经历关联 CRUD listRelations() 440 · addRelation() 452 · deleteRelation() 490 · cascadeDeleteRefs() 499
引用完整性 删除时的引用检查与级联(P0-3) findMessageRefs() 515 · softDeleteRefs() 535
记忆检索 旧记忆 + 高光 + 经历的上下文组装(P0-1/P1) scanMemoryPrefix() 566 · listMemoryKeys() 591 · tokenize() 620 · retrieveMemory() 641 · retrieveContext() 695 · buildSystemPrompt() 716
K 模型调用 多服务商代理 callModel() 847
L 解析层 回复标记解析 parseFavorites() 931([[fav:…]])· parseStatus() 952([[status:…]])
N 主动消息 AI 主动搭话(按 profile 限额) proactiveState() 980 · bumpProactive() 1014
静态资源 PWA pngResponse() 1036 · manifestResponse() 1050
O 安全层 令牌/同源/限流/SSRF/脱敏 toAppError() 1124 · shortId() 1129 · clientIp() 1137 · logEvent() 1146 · corsHeaders() 1153 · isSameOrigin() 1168 · getToken() 1193 · safeEqual() 1198 · checkAuth() 1209 · hashShort() 1214 · bumpCounter() 1226 · checkRate() 1249 · allowedHosts() 1263 · isForbiddenHost() 1271 · validateApiUrl() 1290 · cleanText() 1330 · stripMarkers() 1337 · sanitizeErrorText() 1344 · pageResponse() 1354 · handleBackup() 1377
主处理 聊天 / 主动消息入口 handleChat() 2247 · handleProactive() 2444

关键源码摘录(理解系统行为的六个点)

① 记忆按 profile 隔离(P0-1,修掉跨角色泄漏)

async function saveMemory(env, profile, role, content, time) {
  const p = VALID_PROFILE.test(String(profile || '')) ? String(profile) : DEFAULT_PROFILE;
  const key = `mem:${p}:${ts}_${rand}`;            // 旧格式为 mem_<ts>_<rand>(全局)
  await env.CHAT.put(key, val);
  await env.CHAT.put('meta_lastTs:' + p, String(ts));   // 最后聊天时间也按 profile
  if (p === DEFAULT_PROFILE) await env.CHAT.put('meta_lastTs', String(ts)); // owner 兼容旧键
}

② 消息归档幂等(P0-2,修掉重试重复入档)

async function saveArchive(env, profile, rec) {
  if (!rec || !rec.mid || !rec.content) return { skipped: true };
  if (await env.CHAT.get(key)) return { skipped: true };   // 同 mid 只写一次
  await env.CHAT.put(key, val);
}

③ 鉴权闸门(所有 POST 在进入任何业务逻辑前拦截)

const protectedRoute =
  request.method === 'POST' ||
  url.pathname === '/proactive' ||
  url.pathname === '/backup' ||
  url.pathname === '/init';
if (protectedRoute && !checkAuth(request, env)) {
  return json({ error: 'unauthorized', rid }, 401);
}

getToken(env) = env.AUTH_TOKEN || DEFAULT_AUTH_TOKEN(1193 行)。令牌同时通过 pageResponse() 以 window.__CAT_TOKEN__ 注入页面——这意味着知道线上地址的人就能从页面里拿到访问令牌(已知限制第 1 条,见第 8 节)。

④ 提示词组装(P1,记忆闭环)

function buildSystemPrompt(persona, time, ctx) {
  // ctx 来自 retrieveContext():旧记忆 top5 + 高光 top3 + active 经历 top3 + 候选经历 top2
  // 三段新增:长期高光原话 / 已确认经历 / 候选经历(明确标注"不得当作事实")
}

⑤ 模型上下文检索(P1)

async function retrieveContext(env, profile, query, time, limit = 5) {
  // 同时取四路,全部按 profile:mem:<profile>:* / hl:<profile>:* / exp:<profile>:*(active|candidate)
}

⑥ 聊天主入口行为(handleChat,2247 行起)

2.3 路由表(全部接口)

路径 方法 鉴权 说明
/ 及其他未匹配路径 GET 否 返回前端页面(运行时注入 __CAT_TOKEN__)
/ 及其他未匹配路径 POST 是 聊天主接口(任意 POST 路径都走这里)
/manifest.webmanifest GET 否 PWA manifest
/apple-touch-icon.png /icon-192.png /icon-512.png GET 否 PWA 图标
/health GET 否 健康检查 {ok, service, version:"2.0"},不含配置密钥
/init POST 是 初始化 KV schema(兼容保留)
/backup GET 是 导出最近 N 条记忆(?limit=200)
/archive GET 是 档案导出;支持 keyword/from/to/speaker/limit/offset 搜索分页(P1),过滤软删
/archive POST 是 档案幂等导入
/export/full GET 是 全量导出(schema 2:消息+高光+经历+关系+计数)(P0-4)
/import/full POST 是 全量导入:dry_run 预览、幂等、逐条 errors/warnings(P0-4)
/highlights GET/POST/DELETE 是 高光库;写入强制引用真实 msg:<profile>:<mid>(P0-2)
/experiences GET/POST/DELETE 是 经历记忆;source_mids 必须全部存在
/relations GET/POST/DELETE 是 关联关系;两端必须存在
/proactive POST 是 主动消息轮询(每日上限 + 最小间隔,按 profile)
/messages/delete POST 是 删除消息:mode=soft\|hard、force;有引用且无 force → 409 has_refs;force → 级联软删(P0-3)

请求体上限 SEC.MAX_BODY = 8MB(P0-4 为全量导入从 1MB 提升);单条消息 4000 字;图片 800KB。

2.4 KV 键位图(CHAT 命名空间)

键模式 内容 备注
mem:<profile>:<ts>_<rand> 长期记忆(role/content/ts/day_key/hour/period) P0-1 新格式
mem_<ts>_<rand> 旧格式记忆 仅 owner 只读兼容,开关 MEM_READ_LEGACY
meta_lastTs:<profile> / meta_lastTs 最后聊天时间(主动消息间隔判断) 后者为 owner 兼容旧键
msg:<profile>:<mid> 结构化消息档案(原文/角色/时间/附件/deleted_at) 阶段 2/3 引入
hl:<profile>:<id> 高光(原话 + 来源 mid + 分类) 阶段 4 引入
exp:<profile>:<id> 经历(active/candidate + certainty + source_mids) 阶段 4 引入
rel:<profile>:<id> 关系(高光↔经历,deleted_at 软删) 阶段 4 引入
proactive:<profile>:<day> / proactive_<day> 主动消息每日计数 后者为旧键
rl_<hash> / rl_global 限流计数(分钟/日/全局三级,TTL 172800 秒) 安全层

<profile> 默认 owner,校验 ^[a-z0-9_]{1,32}$;测试角色必须用不同 profile_id 与正式数据隔离。

2.5 前端源码地图(index.html,改 UI 前先看《模块索引.md》)

区域 名称 关键选择器 / ID / 函数
A 顶部栏 .header #fullscreenBtn #openSetting #multiHeader #multiDel;updateFsBtn()
B 消息区 #chatBox .msg-row .bubble .bubble-img .msg-status-text .time-divider;appendMessage() updateMessage() refreshTimeDividers()
C 底部输入栏 #messageInput #sendBtn #pawBtn #imgPreviewArea #replyBar;sendMessage() requestChat() compressImage()
D 猫爪面板 照片/表情包/颜文字/收藏四页签;#pickPhotoBtn #photoSheet #pawPanel;openPawPanel() switchTab() renderFavorites()
E 设置面板 #settingModal:状态喵/头像/外观气泡/后端与模型/数据备份;#exportBackupBtn #importBackupBtn #cloudSyncBtn #cloudRestoreBtn #memoryBtn #memoryModal
F 通用弹层 灯箱 .img-viewer、showToast()、确认弹窗 #confirmModal、长按菜单 #longMenu
G 数据层 localStorage 设置 + IndexedDB 消息历史;readMessages() saveMessages() normalizeMessages();保护键 messages_corrupt messages_pre_restore
H 接口层 requestChat(text, img, clientMid)、checkProactive()、worker_url 取值
I 外观 CSS 变量 --bubble-me-* --bubble-ai-* --topbar-* --bottombar-*;applyAppearance() applySkinClass()

P0-2 起前端新增的关键字段/行为:每条消息对象带 mid(c_<ts>_<rand>);requestChat 携带 client_mid;重试复用同一 mid(幂等);收藏/云同步/恢复携带真实 mid;多选删除先 POST /messages/delete 成功后再删本地;AI 的 [[fav:]] 也进入云端高光。

2.6 六条主数据流

  1. 发消息:sendMessage() → 生成 mid → 本地渲染 → requestChat(text, img, clientMid) POST → Worker 鉴权/限流 → 按 profile 检索上下文 → 组提示词 → callModel → 解析标记 → saveArchive 入档 → 返回 {reply, user_mid, ai_mid} → 前端回写 mid 与状态。
  2. 重试:复用同一 mid → Worker 发现档案已存在且内容一致 → 跳过写入(不重复归档),但仍调用模型生成新回复(新 ai_mid)。
  3. 多选删除:前端收集 mid → POST /messages/delete(默认 soft)→ 有引用返回 409 列表 → 用户确认 force → 级联软删 → 成功后本地再删 → 失败保留本地并提示(防“恢复复活”)。
  4. 云同步:cloudSyncToServer() → POST /archive(带 m.mid)→ 幂等写入 msg:<profile>:<mid>。
  5. 云恢复:cloudRestoreFromServer() → GET /archive → 合并本地;恢复前本地另存 messages_pre_restore。
  6. 全量导出/导入(API,暂无 UI):GET /export/full → JSON(schema 2)→ 异机 POST /import/full?dry_run=1 预览 → 正式导入逐条报告。

3. 功能介绍(完整清单)

3.1 聊天核心

3.2 AI 行为

3.3 多服务商模型

前端可选 OpenAI / DeepSeek / 通义 / 自定义,Key 存浏览器本地,经 Worker 代理;validateApiUrl() 白名单 + https + 443 + 禁内网 IP + 禁重定向。

3.4 外观定制(区域 I)

全局底色、顶栏色、底栏色、双方气泡颜色/透明度/磨砂模糊、气泡文字色、自定义气泡皮肤(PNG 九宫格 border-image-slice:33%)、全屏模式、状态文字贴头像定位。

3.5 备份与恢复(三层)

层 入口 内容
本地导出/导入 设置 → 数据备份 localStorage 设置 + IndexedDB 消息全量;导入前自动另存 messages_pre_restore,加载异常时保护为 messages_corrupt
云端同步/恢复 设置 → 云同步/恢复 消息档案 msg:<profile>:<mid> 幂等同步、按 profile 恢复
全量导出/导入(API) GET /export/full / POST /import/full schema 2:消息+高光+经历+关系+计数;dry_run 预览;幂等;逐条 errors/warnings;软删记录保留

3.6 记忆系统(阶段 2–4 + P0–P1)

能力 存储 进入模型上下文
长期记忆(旧) mem:<profile>:*,关键词检索 是(top5)
高光原话库 hl:<profile>:*,必须引用真实消息 是(top3)
经历记忆 exp:<profile>:*,active/candidate + 确信度 是(active top3 + candidate top2)
关联关系 rel:<profile>:*,高光↔经历 否(界面时间线/关联查看)
记忆库 UI 设置 → 记忆库(三标签面板) —

3.7 安全层(安全说明.md 有完整设计)

访问令牌认证(任意 POST / /proactive /backup /init 必须带头)、同源校验、三级限流(IP 分钟/IP 日/全局,KV 计数)、请求体与消息体积限制、出站地址白名单与 SSRF 防护、提示词注入边界(<<< >>> + 最高优先级安全段)、错误脱敏(对外只给错误码 + rid)、日志 rid 追踪。

3.8 PWA

manifest + 192/512/apple-touch 三图标(icons/gen_icon.py 生成),可“添加到主屏幕”。


4. 架构与数据流

4.1 总体架构

┌─────────────────────────── 浏览器 ───────────────────────────┐
│ index.html(单页)                                            │
│  UI 层(区域 A-F)   数据层 G(localStorage + IndexedDB)       │
│  接口层 H(requestChat / checkProactive / 云同步 / 全量导入)   │
└───────────────┬──────────────────────────────────────────────┘
                │ HTTPS(带令牌 X-Cat-Token / body.token)
┌───────────────▼──────────────────────────────────────────────┐
│ Cloudflare Worker(cat-chat-all-in-one.js)                   │
│  安全闸门:同源 → 令牌 → 限流 → 体积 → 路由分发                  │
│  /chat(任意POST)  /archive  /export|import  /highlights...  │
│  /proactive  /messages/delete  /backup  /init  /health        │
│  检索(retrieveContext)→ 提示词(buildSystemPrompt)          │
│  → callModel(出站白名单)→ 解析(status/fav)→ 入档            │
└───────────────┬──────────────────────────────────────────────┘
                │
┌───────────────▼──────────────────────────────────────────────┐
│ Cloudflare KV(CHAT):mem:* / msg:* / hl:* / exp:* / rel:*   │
│                      meta_lastTs:* / proactive:* / rl_*       │
└───────────────────────────────────────────────────────────────┘
                │ 出站
┌───────────────▼──────────────────────────────────────────────┐
│ 大模型 API(OpenAI 兼容,多服务商,Key 由前端提供)             │
└──────────────────────────────────────────────────────────────┘

4.2 存储分层

层 存什么 丢失后果
localStorage 设置、外观、模型配置、worker_url、令牌覆盖 可重设
IndexedDB(messages) 本地聊天历史 可从云端恢复
KV(云端) 档案、记忆、高光、经历、关系 仅剩本地

4.3 一次聊笑的完整链路(按代码顺序)

请求 → CORS/同源 → 令牌闸门(④) → 路由匹配 → 限流 checkRate → 体积检查 → handleChat → profileOf 定 profile → 解析 client_mid → saveArchive(幂等)→ retrieveContext(四路检索)→ buildSystemPrompt → callModel(validateApiUrl → fetch → 错误脱敏)→ parseStatus/parseFavorites → saveMemory + 高光/收藏入库 → 返回 {reply, user_mid, ai_mid, should_*} → 前端回写 mid/状态。


5. 开发进程记录

5.1 Git 全历史(20 个提交,git log --oneline)

# 提交 阶段 内容
1 afa0aa4 基线 猫爪聊天 v2 完整版(功能 + UI 定制 + 安全加固)
2 352bc65 基线 取消默认人设:只用自定义人设,无则完全不放人设段
3 790e7a8 基线 文档:模块索引区域 J 去除 DEFAULT_PERSONA 引用
4 9f3a385 基线 部署取消默认人设版本 3c0cd4a7,token 恢复存入本地
5 1b18312 基线 修复自定义人设不生效:人设优先级声明 + 历史角色隔离
6 d0bd491 UI 输入栏贴底去描边、顶栏去胶囊、删备注与深色模式、时间条白字深影
7 f2c6dbd 阶段 0 只读审计:顶栏白条修复部署 + 提交源码审计报告(不改正式代码)
8 08815a9 阶段 1 UI v2(去标题/设置贴右上/底部悬浮胶囊)+ 修 B1 误清空 + 完整备份恢复
9 7702738 UI 顶栏改零占位悬浮层(去透明方块)+ 输入栏贴近 Home 指示条
10 b3cba15 文档 补录续十二
11 e6c9661 阶段 2/3 统一 profile_id 标识 + 完整云端档案与同步(本地↔云端)
12 ca44bb8 阶段 4 记忆系统:高光原话库 + 经历记忆 + 时间线/关联(后端 + 前端记忆库面板)
13 75657e0 文档 续十三(阶段 2/3)+ 续十四(阶段 4)+ 模块索引 P 区 + 记忆库效果图(= main 顶点)
14 9505597 P0-1 记忆按 profile 隔离(mem:<profile>:*),旧 mem_* 仅 owner 只读兼容
15 513f24b P0-2 稳定 client_mid 幂等归档 + AI 独立 mid + 高光/经历/关系引用真实消息
16 d4aa34e P0-3 新增 /messages/delete(软删/硬删 + 引用检查 + 级联软删),archive 过滤软删,前端先服务端后本地
17 2460f9a P0-4 全量导出/导入(schema 2、dry_run 预览、幂等、错误/警告报告、图片附件元数据、软删保留)
18 5bfe8c8 P1 高光/经历/候选进入模型上下文(带来源、候选单列)+ 档案搜索
19 5b01441 构建修复 build.js 改为由 worker.js 重新生成部署文件(消除后端漂移)+ 阶段 4 v2 回归
20 b03a612 文档 P0-P1 修复记录(当前 HEAD)

分支:fix/p0-memory-consistency(14–20,7 个提交,未推送)|main = 75657e0(2026-10-19 15:49)。

5.2 阶段 0–4 简述

阶段 目标 落点
0 只读审计,定位顶栏白条等显示问题 提交 f2c6dbd,只出报告不改码
1 修 B1 误清空 + 完整备份恢复 + UI v2 08815a9
2/3 统一 profile 标识 + 云端结构化档案与同步 e6c9661
4 记忆系统(高光/经历/关系 + 记忆库面板) ca44bb8

5.3 审计进程(修复前发生了什么)

  1. 交接只读检查 ×3:仓库地址/私有性/备份时间/未提交项;两个未跟踪截图文件(新界面-v3-贴底无方块.png、新界面-顶部无壳.png,UI 效果图,无功能作用);部署版本证据(Cloudflare API 只读确认 f490d9d2 在线,早于本轮修复)。
  2. 完整只读审计:以任务书为验收基准,输出功能清单、架构与数据流、逐条需求验收表、风险清单、证据索引。发现高危项:mem_* 跨 profile 泄漏、高光/经历不进模型上下文、mid 断链、重试重复入档、本地删除不落云端导致恢复复活、生产 KV 无全量备份、令牌随页面公开下发。
  3. 第二轮只读复核:逐项追踪泄漏条件、ID 不一致点、重复入档与云端复活是必然还是条件性,标注证据等级与置信度。
  4. 夜间自主推进(只读 + 本地虚构数据):交叉复核、整理可重复测试(/workspace/小手机-审计草案/,含 21/21 通过的隔离测试)、分阶段修复方案、独立草案补丁、交接文档。未改正式源码、未提交、未部署、未读真实数据。

5.4 修复阶段(P0-1 → P0-4 + P1,每个都先测试后提交)

项 问题(审计实证) 修法 测试
P0-1 mem_* 全局键:图片消息/主动消息无条件跨 profile 泄漏,文本关键词重叠时条件泄漏 新键 mem:<profile>:*;旧键仅 owner 只读(MEM_READ_LEGACY);meta_lastTs/主动计数全部按 profile 15/15(含图片、主动、关键词重叠、旧数据仅 owner)
P0-2 前端本地数字 id 与档案 u_/a_ 断链;重试重复入档;高光可引用不存在的消息 client_mid 随请求;saveArchive 幂等;同 mid 不同内容 409;高光/经历/关系强制引用真实 msg: 键(STRICT_MID) 21/21
P0-3 本地删除不落云端,云恢复后“复活” 新增 /messages/delete(soft/hard + 引用检查 409 + 级联软删);/archive 过滤 deleted_at;前端先服务端后本地 23/23
P0-4 生产 KV 无全量备份手段 /export/full + /import/full(schema 2、dry_run、幂等、逐条报告);MAX_BODY 1MB→8MB 27/27
P1 高光/经历/关系完全不进模型请求,记忆系统对 AI 不可见 retrieveContext 四路检索;buildSystemPrompt 三段注入(候选单列并标注“不得当作事实”);/archive 搜索分页 13/13 + 14/14
构建 部署文件后端停在旧版(漂移) build.js 整体重生成;SHA256 前缀一致校验 阶段4 v2 32/32(worker.js + 部署文件双模块)

历史 test_phase4.mjs 有 12 项失败,原因是旧测试用假 mid(u_111)被新的严格引用校验拒绝——属测试数据过期,非功能回归,已由 test_phase4_v2.mjs(真实来源)替代。【实测】

5.5 当前 Git 状态(2026-10-22)


6. 测试与证据

6.1 测试矩阵(全部本地运行,内存 Mock KV + Mock 模型 + 虚构数据 + 虚构令牌)

测试文件 结果 覆盖
tests/test_p0_memory_isolation.mjs 15/15 记忆跨 profile 隔离(正/反/图片/主动/关键词重叠/旧数据仅 owner)
tests/test_p0_mid_refs.mjs 21/21 mid 回写、重试幂等、409 冲突、引用校验、图片元数据
tests/test_p0_delete_sync.mjs 23/23 软删/硬删、引用 409、级联、防复活、profile 隔离删除
tests/test_p0_export_import.mjs 27/27 全量导出/导入、dry_run、幂等、错误/警告、软删保留
tests/test_p1_memory_context.mjs 13/13 高光/经历进 prompt、候选分离、跨 profile、条数上限
tests/test_p1_archive_search.mjs 14/14 关键词/时间/说话方/分页/软删过滤
tests/test_phase4_v2.mjs 32/32 阶段 4 原功能回归(worker.js + 部署文件各 16 项)
历史回归(/tmp,修复期跑过) phase1 10/10 · phase23 14/14 · ui_v2 32/32 · ui_v3 12/12 UI 与原功能回归

6.2 构建一致性【实测】

6.3 复跑方式

cd /workspace/chat
for f in tests/test_p0_memory_isolation.mjs tests/test_p0_mid_refs.mjs \
         tests/test_p0_delete_sync.mjs tests/test_p0_export_import.mjs \
         tests/test_p1_memory_context.mjs tests/test_p1_archive_search.mjs \
         tests/test_phase4_v2.mjs; do node "$f"; done

语法校验(对部署文件):cp cat-chat-all-in-one.js /tmp/w.mjs && node --check /tmp/w.mjs。

6.4 尚未覆盖(诚实声明)


7. 交接信息

7.1 仓库与备份

项 状态
GitHub 仓库 https://github.com/beibbloveme-crypto/yuai.git(git remote -v;公开/私有属性与远端实时状态【未验证】)
最近一次进入远端的提交 75657e0(2026-10-19 15:49,= main);此后 7 个修复提交只在本地
未提交修改 2 个未跟踪 PNG(UI 效果图);无已修改未暂存文件
本地即最新 是——所有代码在 /workspace/chat/ 工作区,丢失本地 = 丢失 P0–P1 修复

7.2 部署

项 值
线上 Worker zhai,部署文件 cat-chat-all-in-one.js
当前线上版本 f490d9d2-8ee5-4d0c-9274-b082b5d34990,2026-10-19T07:14:23Z 起 100% 在线【Cloudflare API 只读查询】
与本地关系 线上早于 P0–P1 修复,跑的是修复前代码
回滚锚点 若部署出问题:npx wrangler rollback f490d9d2-8ee5-4d0c-9274-b082b5d34990
查询版本 npx wrangler deployments list --json(只读)

7.3 敏感信息处理

7.4 最小交接文件清单

类别 文件
必需 index.html worker.js build.js wrangler.toml icons/(3 png + gen_icon.py)tests/(7 个)
可选 各 .md 文档、deploy.sh、新界面-*.png 效果图
敏感(勿外传) wrangler.toml 真实值、.wrangler/、任何含真实 KV 数据的导出文件
可重新生成 cat-chat-all-in-one.js(node build.js)

8. 已知限制与技术债

  1. 令牌随页面公开下发:pageResponse 把 __CAT_TOKEN__ 注入页面,任何能打开线上地址的人都能拿到访问令牌(可读写该 Worker 的全部数据)。缓解:轮换 AUTH_TOKEN secret、后续阶段改独立强认证(阶段 7)。这是当前最大安全面。
  2. 图片本体不入云档:云端只存 message_type:'image' 与附件元数据;图片本体恢复需 R2(未启用、涉及付费、未授权)。
  3. 旧本地消息无 mid:更早的本地消息只能本地删除;若曾被归档,云端记录可能仍在。
  4. 重试会生成新的 AI 回复:用户消息幂等,但每次重试仍调模型、写新 ai_mid。
  5. 搜索为扫描式:最多扫 20,000 key,无索引;数据量大时 KV 读成本上升。
  6. 全量导入上限 8MB:更大备份需分批。
  7. STRICT_MID 默认开启:外部脚本用假 mid 建高光/经历/关系会得 400(刻意的完整性约束)。
  8. 旧 mem_* 不迁移不删除:仅 owner 只读;其他角色不可读。
  9. 全量导出/导入、档案搜索暂无前端 UI:目前是受令牌保护的 API;现有“云端同步/恢复”按钮继续可用。
  10. 未部署:线上仍为 f490d9d2,本地修复未上线。
  11. index.html 存在同名选择器重复定义(.avatar .header .bubble 等 2 份以上,以最后一份为准)——改样式前必须 grep -n "选择器{" 数一遍(《模块索引.md》第五节有清单)。
  12. cat-chat-all-in-one.js 是构建产物但被 Git 跟踪:历史上它才是“真正部署的文件”,保留跟踪便于回滚;修改必须经 build.js。

9. 待办与后续阶段

9.1 立即可做(需你授权)

事项 前提
git push 分支 fix/p0-memory-consistency 到 origin 你批准推送
部署到线上(node build.js && npx wrangler deploy) 你批准部署;建议先轮换 AUTH_TOKEN
前端补全量导出/导入 UI(接口已就绪) 你批准改前端
前端补档案搜索面板 + “跳转原消息/上下文”(接口已就绪) 同上
图片本体入 R2 涉及付费(开通 R2 需绑定支付方式),需你明确授权和预算

9.2 原任务书后续阶段(未开始)

阶段 内容
5 人设四层 + 版本管理
6 成长队列
7 后台独立强认证(解决第 8.1 条令牌问题)
8 数据迁移
9 清理

10. 常用操作手册

# 改前端后重新生成部署文件(必须!)
cd /workspace/chat && node build.js

# 语法校验部署文件
cp cat-chat-all-in-one.js /tmp/w.mjs && node --check /tmp/w.mjs

# 跑全部测试
for f in tests/test_*.mjs; do node "$f"; done

# 本地起前端(file:// 下 localStorage 被禁用,必须走 http)
python3 -m http.server 18924

# 查看与 main 的差异(可审查)
git diff main..HEAD --stat
git log main..HEAD --oneline

# 只读查询线上部署版本
npx wrangler deployments list --json

# 回滚线上(紧急)
npx wrangler rollback f490d9d2-8ee5-4d0c-9274-b082b5d34990

# 代码回滚(放弃本地修复)
git checkout main
# 或逐个 revert
git revert 5b01441 5bfe8c8 2460f9a d4aa34e 513f24b 9505597

附录 A · 证据索引

结论 证据位置
修复内容与提交级记录 修复记录-P0-P1.md(仓库内,已提交)
改代码前的区域地图 模块索引.md(仓库内)
安全层设计 安全说明.md(仓库内)
阶段 0 审计 审计报告-阶段0.md(仓库内)
审计草案(复核报告/修复方案/测试方案/交接补全) /workspace/小手机-审计草案/01–04*.md + test_cross_profile_and_ids.mjs(21/21)
隔离测试(跨 profile + ID) /workspace/小手机-审计草案/test_cross_profile_and_ids.mjs
线上版本证据 Cloudflare API 只读查询(2026-10-19/20 多次,f490d9d2 在线)
测试原始输出 本文第 6 章 + 修复记录-P0-P1.md 第四节

附录 B · 术语表

术语 含义
profile 角色标识(默认 owner),所有 KV 数据按它隔离;测试角色必须用不同值
mid / client_mid 消息唯一 ID;client_mid 由前端生成(c_<ts>_<rand>),AI 消息为 a_<ts>_<shortId>
高光 值得记住的原话,必须引用真实消息
经历 从对话沉淀的事件记忆,分 active/candidate
关系 高光↔经历的关联
软删 记录打 deleted_at 不物理删除,导出/检索时过滤,可恢复
漂移 部署文件与源码不一致(已修复)
STRICT_MID 严格引用校验开关(默认开)
MEM_READ_LEGACY 旧 mem_* 兼容读取开关(默认开,仅 owner)

本文档由本地只读检查 + 实际运行测试生成;未修改任何源码、未提交、未部署、未读取任何真实用户数据。

本页由交接包内的 Markdown 原样转换;内容未改动。源码与完整包请回目录页下载。