审查报告

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

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

生成时间: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 原样转换;内容未改动。源码与完整包请回目录页下载。