Skip to content

Releases: xscanzm/recall

0.5.12

Choose a tag to compare

@github-actions github-actions released this 28 Aug 09:30
v0.5.12

chore(release): 版本提升至 0.5.12 并更新发布说明

0.5.11

Choose a tag to compare

@github-actions github-actions released this 06 Aug 14:03
v0.5.11

chore(release): 版本提升至 0.5.11 并更新发布说明

0.5.10

Choose a tag to compare

@github-actions github-actions released this 05 Aug 11:44
v0.5.10

v0.5.10

0.5.9

Choose a tag to compare

@github-actions github-actions released this 03 Aug 02:21

v0.5.9 — 安全加固 + Electron 43 升级 + 渲染层健壮性

本次版本以安全加固为主线:收紧更新安装、IPC 与更新下载链路的安全边界,为 Worker 默认模型代理加入按 IP 限流;同时升级 Electron 至 43.2.0 并启用 ASAR 完整性校验,补齐隐私数据一致性、数据库查询护栏与渲染层健壮性,并恢复测试覆盖率真实口径。

修复

1. 更新与 IPC 安全加固

  • 安装包路径可信化update:installAndQuit 移除渲染层可控的安装包路径参数,改为主进程内部已验证路径,配合 updates 目录守卫与契约严格校验,杜绝渲染层注入任意可执行路径
  • IPC 全链路来源校验:所有 handler 增加 senderFrame 校验(fail-closed:null frame / 子 frame / 未注册窗口一律拒绝)
  • 更新下载 URL 主机白名单:仅允许 recall-update.ppclaw.online,其余主机一律拒绝

2. Worker 默认模型代理按 IP 限流

  • 基于 D1 原子计数实现按客户端 IP 每日限流,状态轮询请求豁免,超限返回 429 + Retry-After

改进

1. 隐私与数据一致性

  • 调试载荷自动清理model_jobs 调试载荷默认保留 30 天自动删除,保留时长可用环境变量调整
  • 级联删除事务化memory:deleteObject 级联删除纳入事务,任一环节失败整体回滚
  • 文档与代码事实对齐:README / doc 07 / worker README 的隐私声明与代码事实对齐(默认模型服务、遥测边界、R2 临时保留)

2. 数据库

  • 分页护栏:observations / facts 时间范围查询增加 LIMIT 分页(默认 200)
  • 动态 SQL 标识符校验:运行时动态 SQL 标识符增加允许集合校验(fail-closed)
  • 复合索引:新增 timeline_blocks 复合索引 (date_key, start_at)(迁移 030)

3. 渲染层健壮性

  • 乱序响应守卫:搜索 / 项目详情页快速连续操作不再被旧响应覆盖
  • IPC 调用错误处理补全 + 定时器清理
  • 对话框焦点陷阱 / Tab 循环 / Escape 关闭、tab 键盘导航、原生 alert/confirm 替换
  • 列表组件 React.memo 化、删除死组件 ReportEditor、内联样式迁移

4. 工程与升级

  • Electron 32 → 43.2.0(含 better-sqlite3 11→13、electron-builder 26),启用 ASAR 完整性 fuses(EnableEmbeddedAsarIntegrityValidation + OnlyLoadAppFromAsar
  • 测试覆盖率真实口径:vitest 恢复 all: true 全量口径,基线 31.9 / 68.66 / 55.36 / 31.9,只升不降 ratchet 门禁
  • UpdateService 核心路径单测:函数覆盖 92.3%
  • CI 门禁增强:macOS 覆盖率、发布依赖测试 CI(workflow_call 跨文件 gate)、e2e 产物上传、test:maintenance 接入
  • maintain-recall-data 跨平台默认路径
  • LinkerSceneJudgeWorker 按职责拆分,ID 生成 / 日期 / 时区工具统一,handlers.ts 通道模块化迁移(234 行)

已知限制

  • 窗口拖拽在缩放显示器上存在约 4.5× 灵敏度偏差(Chromium 144 screenX 反馈所致,无崩溃影响,CI 正常屏幕不受影响)

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • UpdateService 核心路径单测通过(函数覆盖 92.3%)
  • 本地构建与 NSIS 安装包打包通过

v0.5.7 — macOS 客户端完整支持 + 免证书解隔离助手 + GitHub Actions 云端打包

本次版本正式发布 macOS 客户端支持,提供全平台原生 Worker 编译支持、macOS 屏幕录制/辅助功能权限引导、DMG 免证书解隔离安装助手及 GitHub Actions 自动云端打包流程。

新增与改进

1. macOS 免证书解隔离安装助手

  • DMG 一键修复脚本:资源中加入可执行 Bash 脚本 双击修复安装.command,解决非商业签名应用在 macOS 上被提示“应用已损坏,无法打开”的问题,自动解除 com.apple.quarantine 隔离属性。
  • 文档与官网支持:README 与官网新增 macOS 下载入口及 Mac 初次安装权限解锁说明。

2. macOS 权限与隐私系统适配

  • MacPermissionsService:支持检测 macOS 屏幕录制 (Screen Capture) 和辅助功能 (Accessibility) 权限状态,并提供一键跳转系统隐私设置面板接口。

3. Worker 跨平台运行与进程清理

  • 适配 macOS 上无 .exe 扩展名的二进制 Worker 可执行文件(rapidocr-worker)查找与运行机制;
  • 增强 POSIX 进程树清理 (process.kill(-pid)),确保应用退出或引擎终止时 Mac 平台常驻 Worker 无残留;
  • 新增 scripts/build-rapidocr-worker.sh macOS 平台 Worker 打包脚本。

4. GitHub Actions 自动云端打包 (CI/CD)

  • 新增 .github/workflows/mac-ci.yml CI 检查,确保 Mac 环境下的打包构建与单测契约持续通过;
  • 更新 .github/workflows/release.yml 发布流程,当发布 Tag 时自动在 macos-latest 机器上编译产出 Apple Silicon (arm64) 与 Intel (x64) 的 macOS .dmg.zip 镜像并关联提交到 GitHub Release。

v0.5.6 — 更新下载分片 + 断点续传(解决国内 R2 大文件下载不稳定)

本次版本聚焦解决用户反馈的"更新失败"问题:国内访问 Cloudflare R2 下载 180MB+ 安装包时连接不稳定,旧的流式下载一旦中断就必须从头开始,导致更新成功率低。新方案用 HTTP Range 请求分片下载 + 断点续传,中断后可从断点继续,显著提升大文件下载稳定性。

改进

1. UpdateService 分片 + 断点续传下载

重写 downloadUpdate 方法,从单次流式下载改为分片 + 断点续传:

  • HEAD 探测:下载前先发 HEAD 请求探测服务器是否支持 Accept-Ranges: bytes,支持则走分片路径,不支持则回退到原流式下载(保证兼容性)
  • 分片下载:每片 4MB,使用 HTTP Range 请求 bytes=start-end,只下载该范围的数据
  • 断点续传:每片下载成功后立即追加写入 .part 文件并更新 .meta.json 元数据(记录 version/sha256/bytesTotal/bytesDownloaded);进程中断后下次启动从断点继续,无需从头下载
  • 单片超时保护:单片 30 秒超时(AbortController),超时自动重试,避免单个分片卡死整个下载
  • 单片重试:单片最多重试 5 次,退避 1s/2s/4s/8s/16s
  • 整体轮次:最多 6 轮断点续传,每轮从上次中断处继续;连续 20 片失败则中止
  • 元数据严格校验readDownloadMeta 校验 version/sha256/chunkSize/bytesTotal 与 .part 实际大小一致性,脏断点自动丢弃重下
  • 整体 SHA256 校验:下载完成后校验最终文件 SHA256,不匹配则清理所有产物并失败

2. 文件布局与清理策略

  • 文件布局
    • updates/Recall-{version}-setup.exe — 最终安装包
    • updates/Recall-{version}-setup.exe.part — 下载中分片合并文件
    • updates/Recall-{version}-setup.exe.meta.json — 断点续传元数据
  • cleanupIncompleteDownloads 语义调整:只清理 .tmp(流式回退产物),保留 .part + .meta.json 供断点续传;下次启动 readDownloadMeta 会校验 .part 一致性,脏断点自动丢弃重下

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • 本地构建与 NSIS 安装包打包通过

以下为历史版本发布说明:

v0.5.5 — 截图采集架构重构(单窗口捕获 + 遮挡门禁 + 三后端降级链)

本次版本重构截图采集管线,从"为了拍 1 个窗口打扰整个系统"改为"只碰目标窗口、不打扰任何第三方应用"。旧方案 desktopCapturer 为了抓 1 个窗口会对系统里每个顶层窗口发 WM_PRINT 强制同步渲染,导致钉钉等 GPU 合成应用偶发白屏;新方案用 getDisplayMedia + Windows Graphics Capture 只读 DWM 合成表面,零副作用,并加上遮挡门禁杜绝错误/越权采集。

新增功能

1. WindowFrameGrabber(单窗口 getDisplayMedia 捕获)

替代旧的 desktopCapturer.captureWindow 全窗口抓图,只触及目标窗口:

  • 零抓图枚举thumbnailSize:{0,0} 只取 id/name 不产图像,目标筛选发生在产生任何图像之前
  • getDisplayMedia + WGC:用 getDisplayMedia 对那一个窗口开捕获会话,底层走 Windows.Graphics.Capture(DWM 读现成合成表面,不发任何 WM_PRINT),从根上消除对第三方应用的副作用
  • 隐藏 BrowserWindow 抓图宿主:1×1 隐藏窗口通过 setDisplayMediaRequestHandler 把"用户选哪个窗口"替换成我们指定的 source,不弹系统选择器;宿主页 capture-host.html 必须是 file:// 协议(data:/about:blank 是 opaque origin 会被 getDisplayMedia 拒绝)
  • Chromium feature 开关app.ts 启用 AllowWgcWindowCapturer(走 WGC)+ 关闭 AllowWgcWindowZeroHz(避免静止内容不产新帧导致超时)
  • 抓图脚本通过 executeJavaScript 注入宿主页(主进程 tsconfig 无 DOM lib,不值得为 40 行脚本给整个项目加 DOM lib)

2. captureOcclusion(遮挡门禁)

纯函数模块,服务于 screen crop 兜底路径,防止目标窗口被遮挡时裁到错误内容或隐私泄露:

  • findVisibleOccluders:拿 active-win.getOpenWindows() 的 Z 序,只把 Z 序严格在目标之前的窗口算作遮挡;排除自身进程、最小化窗口(Windows -32000 哨兵坐标)、零面积窗口;目标不在列表返回 null(保守不采)
  • computeOccludedRatio:扫描线算矩形并集面积(不是简单累加,避免两个互相重叠的遮挡窗口把比例算过 1)
  • MAX_BENIGN_OCCLUSION_RATIO = 0.35:良性遮挡容忍上限;敏感遮挡(过不了 PrivacyGuard)一律跳过,不看比例

改进

3. CaptureService 三后端降级链

从单一抓图路径改为按"对第三方应用的伤害面从小到大"排序的三后端降级链:

优先级 后端 行为 默认
1 window_display_media 单窗口 getDisplayMedia,只碰目标 启用
2 screen_crop_fallback 抓整屏再裁,需先过遮挡门禁 启用
3 window(旧全窗口缩略图) 把系统里所有窗口都抓一遍 + WM_PRINT 禁用,只留应急开关

关键新增方法:

  • captureSingleFrame:依次试三条后端
  • captureViaWindowFrameSource:调用 WindowFrameSource 接口(结构类型,避免循环依赖 + 测试可注入假实现)
  • checkScreenCropOcclusion:screen crop 前的遮挡门禁
  • analyzeCaptureVisualQuality:检测全黑退化帧(nearBlackRatio / luminanceStdDev / edgeDensity),对付 GDI 抓 GPU 合成窗口返回全黑
  • shouldUseScreenCropFallback:信息量比较,决定是否用裁剪图替换退化的窗口图
  • coalesceCaptureCandidate + runSerializedCapture:候选合并 + 串行化
  • 新增 CaptureBackend 类型 + captureMethod 字段落到 CaptureBundle;新增 occluded / no_safe_backend 两个 skip reason
  • pickReportedBackend:多帧落在不同后端时报告"最偏离首选"的那个,便于事后定位问题

核心产品决策:三条路都走不通时跳过这次采集,而不是用伤害用户其它应用的方式硬采——"少一条记忆,好过把用户正在用的应用打白"。

4. ActivityService 增强

新增两个方法直接服务截图采集:

  • getFreshActiveWindowInfo():屏幕裁剪 fallback 在捕获前后各调用一次,防止窗口切换后裁到其他应用(双重校验:bounds 一致 + windowId/title 一致)
  • getOpenWindowsSnapshot():返回 Z 序窗口列表给遮挡判定用;严格区分 null("不知道"必须保守不采)与空数组("确认没别的窗口")
  • ActivityWindowInfo 新增 boundsprocessId 字段

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • 新增单元测试:
    • CaptureService.test.ts:守住"对第三方窗口的伤害面"不变量——首选后端只碰目标;整屏裁剪必须先证明矩形归自己;旧路径默认走不到;遮挡门禁四类场景全覆盖
    • captureOcclusion.test.ts:扫描线并集算法边界情况(互相重叠、完全重合、不相交、最小化、零面积、阈值边界 0.34/0.36)
    • WindowFrameGrabber.assets.test.ts:钉住宿主页与加载方的同目录契约
    • ActivityService.test.ts:新增活动服务测试
  • 本地构建与 NSIS 安装包打包通过

以下为历史版本发布说明:

v0.5.4 — 混合搜索(FTS5 trigram + BGE 本地 embedding)+ 身份归一化与重复对象审计

本次版本聚焦记忆搜索能力升级与对象身份治理:搜索从单一 LIKE 全表扫描升级为 FTS5 trigram 词法召回 + BGE 本地 embedding 语义召回的双路混合检索,支持中文子串精确匹配与"意思相近但字面不同"的语义召回;同时引入身份归一化工具与只读审计服务,解决历史数据中同名不同人或同人异名的问题。

新增功能

1. 混合搜索(HybridSearchService)

将记忆搜索从单一 LIKE '%关键词%' 全表扫描升级为 FTS5 trigram 词法召回 + BGE 本地 embedding 语义召回的双路混合检索:

  • migration 028:重建 memory_search_fts 虚拟表,tokenize 改为 trigram,支持中文任意 3 字符子串精确匹配,查询从 O(N) 全表扫描降到 O(log N) 索引查找;覆盖 7 类对象(facts/scenes/tasks/projects/decisions/people/reports),每类配 INSERT/UPDATE/DELETE 触发器自动维护索引
  • migration 029:新建 memory_embeddings 表(512 维 float32 向量 BLOB 存储)+ memory_embedding_queue 索引队列表
  • HybridSearchService:词法保底(FTS5 top 500)+ 向量召回(cosine 相似度 > 0.15,top 100)→ RRF 融合(k=60,词法权重 1.0,向量权重 0.7)→ 精确匹配提权(exact_id +10、exact_title +5)→ 静默降级(向量路超时 1500ms 或失败时自动回退纯 FTS)
  • MemorySearchResponse 新增 quality: "strong" | "weak" | "none" 字段,区分双路命中、单路命中、无结果
  • 观察数据走独立的 observation_search_fts 表,同样带 bm25 排序和 LIKE 降级
  • 新增 getCandidates + batchGetFtsRowsByKeys 批量回填候选详情,消除 N+1 查询

2. BGE 本地 embedding 推理

全本地 CPU 推理的 embedding 服务,无需外网、无需 GPU、无需配置 API Key:

  • 模型:bge-small-zh-v1.5(BGE 中文小模型),512 维,ONNX 量化版(model_quantized.onnx),位于 resources/embedding/bge-small-zh-v1.5/
  • 进程复用:EmbeddingWorkerClient 复用 rapidocr-worker 进程,通过 --mode embedding --model-dir <path> 参数切换模式,减少打包体积和进程数量
  • 进程间协议:stdin 写 JSON 请求({id, type: "query"|"document", texts}),stdout 读 JSON 响应;单批最多 32 条文本,超过自动分块
  • 三种部署形态自动适配:打包态 rapidocr-worker.exe / 开发态本地构建 exe / Python 源码 rapidocr_worker.py
  • 进程崩溃自动重建,通过 childGeneration 机制隔离旧请求,避免僵尸响应污染新进程
  • EmbeddingIndexerService:后台索引循环,generation 字段防覆盖(旧 embedding 结果不会覆盖已被新内容重新入队的对象)
  • MemoryEmbeddingRepository.listVectorscount:max(updated_at) 签名缓存全量向量,避免每次搜索都从 DB 反序列化所有 BLOB

3. 身份归一化与重复对象审计

引入"同名不等于同一身份"的核心约束,解决历史数据中同名不同人或同人异名的问题:

  • 共享工具 src/shared/identity.ts
    • normalizeIdentity:NFKC + trim + 折叠内部空白 + toLowerCase,保留标点(Recall-v1.0Recall.v1.0 不会被误并)
    • comparePersonIdentity:名称 + role/organization 强字段三档判定(strong_field_match / normalized_name_match / conflict_detected
  • IdentityAuditService:只读 dry-run 审计,扫描未归档 projects 和未软删 people,按归一化名称分组,给出三档分类;不运行真实合并、不修改 DB、不把"完全同名"直接判定为同一人物
  • MemoryObjectRepository 新增 findProjectByExactIdentity / findPersonByExactIdentity,基于精确身份查询
  • MemoryObjectAdmissionServiceadmitOrAccumulate 从模糊匹配切换到精确身份匹配,保留 admissionDecidedBy === "user" 的对象不被自动 reassess 覆盖
  • cascadeMark.mergeObjects:新增 mergeNormalizedAliases 用归一化去重别名;人物合并新增组织信息补全;合并流程包成事务确保原子化

改进

4. BatchProcessor lane 化并发

  • 新增 backlog/fresh/wi...
Read more

0.5.8

Choose a tag to compare

@github-actions github-actions released this 31 Jul 08:31
v0.5.8

Release v0.5.8

v0.5.6 — 更新下载分片 + 断点续传

Choose a tag to compare

@xscanzm xscanzm released this 30 Jul 03:53

v0.5.6 — 更新下载分片 + 断点续传(解决国内 R2 大文件下载不稳定)

本次版本聚焦解决用户反馈的"更新失败"问题:国内访问 Cloudflare R2 下载 180MB+ 安装包时连接不稳定,旧的流式下载一旦中断就必须从头开始,导致更新成功率低。新方案用 HTTP Range 请求分片下载 + 断点续传,中断后可从断点继续,显著提升大文件下载稳定性。

改进

1. UpdateService 分片 + 断点续传下载

重写 downloadUpdate 方法,从单次流式下载改为分片 + 断点续传:

  • HEAD 探测:下载前先发 HEAD 请求探测服务器是否支持 Accept-Ranges: bytes,支持则走分片路径,不支持则回退到原流式下载(保证兼容性)
  • 分片下载:每片 4MB,使用 HTTP Range 请求 bytes=start-end,只下载该范围的数据
  • 断点续传:每片下载成功后立即追加写入 .part 文件并更新 .meta.json 元数据(记录 version/sha256/bytesTotal/bytesDownloaded);进程中断后下次启动从断点继续,无需从头下载
  • 单片超时保护:单片 30 秒超时(AbortController),超时自动重试,避免单个分片卡死整个下载
  • 单片重试:单片最多重试 5 次,退避 1s/2s/4s/8s/16s
  • 整体轮次:最多 6 轮断点续传,每轮从上次中断处继续;连续 20 片失败则中止
  • 元数据严格校验readDownloadMeta 校验 version/sha256/chunkSize/bytesTotal 与 .part 实际大小一致性,脏断点自动丢弃重下
  • 整体 SHA256 校验:下载完成后校验最终文件 SHA256,不匹配则清理所有产物并失败

2. 文件布局与清理策略

  • 文件布局
    • updates/Recall-{version}-setup.exe — 最终安装包
    • updates/Recall-{version}-setup.exe.part — 下载中分片合并文件
    • updates/Recall-{version}-setup.exe.meta.json — 断点续传元数据
  • cleanupIncompleteDownloads 语义调整:只清理 .tmp(流式回退产物),保留 .part + .meta.json 供断点续传;下次启动 readDownloadMeta 会校验 .part 一致性,脏断点自动丢弃重下

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • 本地构建与 NSIS 安装包打包通过

v0.5.5 — 截图采集架构重构(单窗口捕获 + 遮挡门禁 + 三后端降级链)

Choose a tag to compare

@xscanzm xscanzm released this 29 Jul 12:11

v0.5.5 — 截图采集架构重构(单窗口捕获 + 遮挡门禁 + 三后端降级链)

本次版本重构截图采集管线,从"为了拍 1 个窗口打扰整个系统"改为"只碰目标窗口、不打扰任何第三方应用"。旧方案 desktopCapturer 为了抓 1 个窗口会对系统里每个顶层窗口发 WM_PRINT 强制同步渲染,导致钉钉等 GPU 合成应用偶发白屏;新方案用 getDisplayMedia + Windows Graphics Capture 只读 DWM 合成表面,零副作用,并加上遮挡门禁杜绝错误/越权采集。

新增功能

1. WindowFrameGrabber(单窗口 getDisplayMedia 捕获)

替代旧的 desktopCapturer.captureWindow 全窗口抓图,只触及目标窗口:

  • 零抓图枚举thumbnailSize:{0,0} 只取 id/name 不产图像,目标筛选发生在产生任何图像之前
  • getDisplayMedia + WGC:用 getDisplayMedia 对那一个窗口开捕获会话,底层走 Windows.Graphics.Capture(DWM 读现成合成表面,不发任何 WM_PRINT),从根上消除对第三方应用的副作用
  • 隐藏 BrowserWindow 抓图宿主:1×1 隐藏窗口通过 setDisplayMediaRequestHandler 把"用户选哪个窗口"替换成我们指定的 source,不弹系统选择器;宿主页 capture-host.html 必须是 file:// 协议(data:/about:blank 是 opaque origin 会被 getDisplayMedia 拒绝)
  • Chromium feature 开关app.ts 启用 AllowWgcWindowCapturer(走 WGC)+ 关闭 AllowWgcWindowZeroHz(避免静止内容不产新帧导致超时)
  • 抓图脚本通过 executeJavaScript 注入宿主页(主进程 tsconfig 无 DOM lib,不值得为 40 行脚本给整个项目加 DOM lib)

2. captureOcclusion(遮挡门禁)

纯函数模块,服务于 screen crop 兜底路径,防止目标窗口被遮挡时裁到错误内容或隐私泄露:

  • findVisibleOccluders:拿 active-win.getOpenWindows() 的 Z 序,只把 Z 序严格在目标之前的窗口算作遮挡;排除自身进程、最小化窗口(Windows -32000 哨兵坐标)、零面积窗口;目标不在列表返回 null(保守不采)
  • computeOccludedRatio:扫描线算矩形并集面积(不是简单累加,避免两个互相重叠的遮挡窗口把比例算过 1)
  • MAX_BENIGN_OCCLUSION_RATIO = 0.35:良性遮挡容忍上限;敏感遮挡(过不了 PrivacyGuard)一律跳过,不看比例

改进

3. CaptureService 三后端降级链

从单一抓图路径改为按"对第三方应用的伤害面从小到大"排序的三后端降级链:

优先级 后端 行为 默认
1 window_display_media 单窗口 getDisplayMedia,只碰目标 启用
2 screen_crop_fallback 抓整屏再裁,需先过遮挡门禁 启用
3 window(旧全窗口缩略图) 把系统里所有窗口都抓一遍 + WM_PRINT 禁用,只留应急开关

关键新增方法:

  • captureSingleFrame:依次试三条后端
  • captureViaWindowFrameSource:调用 WindowFrameSource 接口(结构类型,避免循环依赖 + 测试可注入假实现)
  • checkScreenCropOcclusion:screen crop 前的遮挡门禁
  • analyzeCaptureVisualQuality:检测全黑退化帧(nearBlackRatio / luminanceStdDev / edgeDensity),对付 GDI 抓 GPU 合成窗口返回全黑
  • shouldUseScreenCropFallback:信息量比较,决定是否用裁剪图替换退化的窗口图
  • coalesceCaptureCandidate + runSerializedCapture:候选合并 + 串行化
  • 新增 CaptureBackend 类型 + captureMethod 字段落到 CaptureBundle;新增 occluded / no_safe_backend 两个 skip reason
  • pickReportedBackend:多帧落在不同后端时报告"最偏离首选"的那个,便于事后定位问题

核心产品决策:三条路都走不通时跳过这次采集,而不是用伤害用户其它应用的方式硬采——"少一条记忆,好过把用户正在用的应用打白"。

4. ActivityService 增强

新增两个方法直接服务截图采集:

  • getFreshActiveWindowInfo():屏幕裁剪 fallback 在捕获前后各调用一次,防止窗口切换后裁到其他应用(双重校验:bounds 一致 + windowId/title 一致)
  • getOpenWindowsSnapshot():返回 Z 序窗口列表给遮挡判定用;严格区分 null("不知道"必须保守不采)与空数组("确认没别的窗口")
  • ActivityWindowInfo 新增 boundsprocessId 字段

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • 新增单元测试:
    • CaptureService.test.ts:守住"对第三方窗口的伤害面"不变量——首选后端只碰目标;整屏裁剪必须先证明矩形归自己;旧路径默认走不到;遮挡门禁四类场景全覆盖
    • captureOcclusion.test.ts:扫描线并集算法边界情况(互相重叠、完全重合、不相交、最小化、零面积、阈值边界 0.34/0.36)
    • WindowFrameGrabber.assets.test.ts:钉住宿主页与加载方的同目录契约
    • ActivityService.test.ts:新增活动服务测试
  • 本地构建与 NSIS 安装包打包通过

v0.5.4 — 混合搜索(FTS5 trigram + BGE 本地 embedding)+ 身份归一化与重复对象审计

Choose a tag to compare

@xscanzm xscanzm released this 28 Jul 06:51

v0.5.4 — 混合搜索(FTS5 trigram + BGE 本地 embedding)+ 身份归一化与重复对象审计

本次版本聚焦记忆搜索能力升级与对象身份治理:搜索从单一 LIKE 全表扫描升级为 FTS5 trigram 词法召回 + BGE 本地 embedding 语义召回的双路混合检索,支持中文子串精确匹配与"意思相近但字面不同"的语义召回;同时引入身份归一化工具与只读审计服务,解决历史数据中同名不同人或同人异名的问题。

新增功能

1. 混合搜索(HybridSearchService)

将记忆搜索从单一 LIKE '%关键词%' 全表扫描升级为 FTS5 trigram 词法召回 + BGE 本地 embedding 语义召回的双路混合检索:

  • migration 028:重建 memory_search_fts 虚拟表,tokenize 改为 trigram,支持中文任意 3 字符子串精确匹配,查询从 O(N) 全表扫描降到 O(log N) 索引查找;覆盖 7 类对象(facts/scenes/tasks/projects/decisions/people/reports),每类配 INSERT/UPDATE/DELETE 触发器自动维护索引
  • migration 029:新建 memory_embeddings 表(512 维 float32 向量 BLOB 存储)+ memory_embedding_queue 索引队列表
  • HybridSearchService:词法保底(FTS5 top 500)+ 向量召回(cosine 相似度 > 0.15,top 100)→ RRF 融合(k=60,词法权重 1.0,向量权重 0.7)→ 精确匹配提权(exact_id +10、exact_title +5)→ 静默降级(向量路超时 1500ms 或失败时自动回退纯 FTS)
  • MemorySearchResponse 新增 quality: "strong" | "weak" | "none" 字段,区分双路命中、单路命中、无结果
  • 观察数据走独立的 observation_search_fts 表,同样带 bm25 排序和 LIKE 降级
  • 新增 getCandidates + batchGetFtsRowsByKeys 批量回填候选详情,消除 N+1 查询

2. BGE 本地 embedding 推理

全本地 CPU 推理的 embedding 服务,无需外网、无需 GPU、无需配置 API Key:

  • 模型:bge-small-zh-v1.5(BGE 中文小模型),512 维,ONNX 量化版(model_quantized.onnx),位于 resources/embedding/bge-small-zh-v1.5/
  • 进程复用:EmbeddingWorkerClient 复用 rapidocr-worker 进程,通过 --mode embedding --model-dir <path> 参数切换模式,减少打包体积和进程数量
  • 进程间协议:stdin 写 JSON 请求({id, type: "query"|"document", texts}),stdout 读 JSON 响应;单批最多 32 条文本,超过自动分块
  • 三种部署形态自动适配:打包态 rapidocr-worker.exe / 开发态本地构建 exe / Python 源码 rapidocr_worker.py
  • 进程崩溃自动重建,通过 childGeneration 机制隔离旧请求,避免僵尸响应污染新进程
  • EmbeddingIndexerService:后台索引循环,generation 字段防覆盖(旧 embedding 结果不会覆盖已被新内容重新入队的对象)
  • MemoryEmbeddingRepository.listVectorscount:max(updated_at) 签名缓存全量向量,避免每次搜索都从 DB 反序列化所有 BLOB

3. 身份归一化与重复对象审计

引入"同名不等于同一身份"的核心约束,解决历史数据中同名不同人或同人异名的问题:

  • 共享工具 src/shared/identity.ts
    • normalizeIdentity:NFKC + trim + 折叠内部空白 + toLowerCase,保留标点(Recall-v1.0Recall.v1.0 不会被误并)
    • comparePersonIdentity:名称 + role/organization 强字段三档判定(strong_field_match / normalized_name_match / conflict_detected
  • IdentityAuditService:只读 dry-run 审计,扫描未归档 projects 和未软删 people,按归一化名称分组,给出三档分类;不运行真实合并、不修改 DB、不把"完全同名"直接判定为同一人物
  • MemoryObjectRepository 新增 findProjectByExactIdentity / findPersonByExactIdentity,基于精确身份查询
  • MemoryObjectAdmissionServiceadmitOrAccumulate 从模糊匹配切换到精确身份匹配,保留 admissionDecidedBy === "user" 的对象不被自动 reassess 覆盖
  • cascadeMark.mergeObjects:新增 mergeNormalizedAliases 用归一化去重别名;人物合并新增组织信息补全;合并流程包成事务确保原子化

改进

4. BatchProcessor lane 化并发

  • 新增 backlog/fresh/window 三 lane 调度:BACKLOG_CONCURRENCY = BATCH_CONCURRENCY - 1 = 4,始终保留 1 个槽给最新数据(fresh lane),防止旧积压拖死今日观察
  • 新增 stopAndDrainActive():停止接收新批次并等待已 claiming 批次终态,专供 DataLifecycleService.exclusive() 清理前调用
  • drainThroughCapturedAt 优先调度窗口内批次,避免窗口外积压占满并发槽

5. DataLifecycleService 事务安全

  • exclusive() 包装器:清理前 pauseSources → drain → suspendAndFlush → drain,避免清理与采集并发;active 标志位防止多个清理操作叠加
  • withEmbeddingIndexerPaused():清理期间 stopAndDrain 嵌入索引器,结束后 startBackgroundIndexing,避免索引器读到半删除状态
  • deleteCaptureLedgerCOALESCE(json_extract, ...) 兼容新旧 bundle 格式
  • forgetRecent / clearAll / clearScreenshots 全部走事务,cleanupFiles 返回 complete/partial/failed 三态

6. TimelineHeader 新增"忘掉最近"按钮

  • 时间轴头部新增 Eraser 图标按钮,点击弹确认框后调 forgetRecent("30m") 清理最近 30 分钟数据
  • CSS 配套:.timeline-header__forget-btn 34px 高、13px 字号、hover 态变危险红色调

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • 新增单元测试:HybridSearchService(双路融合 + 降级)、EmbeddingWorkerClient(进程崩溃重建)、MemoryEmbeddingRepository、IdentityAuditService(三档分类 + 只读断言)、mergeSuggestionsFix(Phase 0 身份归一化)
  • migration 028/029 契约测试通过
  • 本地构建与 NSIS 安装包打包通过

v0.5.3 — 批次死锁修复 + 残批攒批优化 + 跨天窗口封窗 + 窗口拖动改进

Choose a tag to compare

@xscanzm xscanzm released this 27 Jul 04:34

v0.5.3 — 批次死锁修复 + 残批攒批优化 + 跨天窗口封窗 + 窗口拖动改进

本次版本聚焦 v0.5.1 时间轴窗口化落地后暴露的批次与窗口协调类 bug 修复,并顺带把窗口拖动从 CSS app-region 改为 IPC + PointerEvent 自定义实现以解决与 React 事件系统的冲突。

Bug 修复

1. 批次重试耗尽死锁(BatchProcessor + CaptureInboxRepository)

症状:崩溃恢复(recoverRunningBatches)与优雅关闭(checkpointRunning)会把 running 写回 pending 而不动 attempts。若那一次正好是第 maxAttempts 次尝试,这条 batch 就落入死区:listProcessableBatchesattempts < maxAttempts 不再挑它,getWindowWatermark 又因为它既非 succeeded 也非 failed 而永久算作 unsettled,于是覆盖它的时间轴窗口再也封不掉。

修复

  • CaptureInboxRepository 新增 failExhaustedBatches(maxAttempts):把重试次数已用尽却仍停在 pending/running 的 batch 落到终态 failedlast_errorretry_exhausted_without_terminal_state
  • 抽取 BATCH_MAX_ATTEMPTS = 3 常量到仓储层,保证 BatchProcessor 的挑选条件与 getWindowWatermark 的终态判定用同一个数
  • getWindowWatermark 的 SQL 补 attempts >= BATCH_MAX_ATTEMPTS → failed 分支,让水位线不再把这种 batch 算成 unsettled
  • BatchProcessor.start() 启动时调用一次 failExhaustedBatches,避免历史死区 batch 卡死新一天的窗口

2. CaptureBatcher 固定 5 分钟定时器抢切近满批次

症状:ActivityService 长会话间隔 5 分钟、内容变化最小间隔 60 秒,真实到帧节奏约 6070 秒一帧,攒满 6 帧要 6 分钟以上。旧的"从第一帧起算固定 5 分钟"定时器会在批次快满时抢先提交,产生大量 45 帧、甚至 1 帧的批次,拉低模型单批质量。

修复

  • 用"空闲 + 年龄上限"双约束替代固定 5 分钟:IDLE_FLUSH_MS = 150s(距离最后一帧超过 150s 才认为活动停了)、MAX_BATCH_AGE_MS = 10min(队首最老一帧不允许超过 10 分钟,与时间轴采集窗口对齐)
  • scheduleFlush 每次入队都重排,让"空闲"这一半随新帧顺延;delay = Math.max(0, Math.min(IDLE_FLUSH_MS, MAX_BATCH_AGE_MS - oldestAge))
  • 新增 oldestQueuedAt 锚点跟踪队首年龄;flush 后剩下的帧从现在起重新计年龄;队列空了清掉锚点
  • flush 失败时帧退回队首,oldestQueuedAt 也要退回 previousAnchor,不能让失败重排把它们"变新"

3. TimelineWindowCoordinator 跨天窗口死锁 + 封窗重复排空 + 回调环形等待

症状 A:跨天窗口里有 batch 永远算不上终态(unsettledCount 归不了零),旧实现会在 sealing 上原地打转,把 48 次循环耗光,今天的窗口永远开不出来。

修复 A:跨天窗口用 force: true 强制封窗,把没结算的部分标成 partial;封窗后若状态仍没变(说明推不动),直接退出,不在同一个窗口上耗光循环。

症状 Bsealing 状态每 5 秒轮询一次,旧实现每轮都调用 captureService.drain() + captureBatcher.flush(),把尚未攒满 6 帧的 L0 队列强制 flush 成单帧 batch。

修复 B:用 preparedSealingWindows: Set<string> 标记,只在该窗口首次封窗时排空 producer,后续轮询不再强制 flush;窗口离开 sealing 状态(成功/失败/空窗)时清理标记。

症状 ConBatchSettled 回调在 advance 正在等待 batch 时触发,会形成 advance → batch → onBatchSettled → advance 的环形等待死锁。

修复 ConBatchSettled 开头检查 this.running,若已有 advance 在跑则直接返回,避免环形等待。

改进

4. 窗口拖动从 CSS app-region 改为 IPC + PointerEvent

问题:CSS -webkit-app-region: drag 与 Electron + React 事件系统有冲突(按钮点击被拦截、拖动不流畅)。

改进

  • 新增 IPC 通道 window:dragphase: start|move|end + screenX/screenY),主进程 appHandlers.ts 维护 dragState(pointer 起点 + 窗口原位置),move 阶段调用 window.setPosition 跟随
  • 渲染层 AppShell.tsx 三个 handler(handleWindowDragStart/Move/End)用 setPointerCapture 保证拖动不丢失,忽略非左键和 button/a/input 元素
  • 应用于 .app-shell__brand(品牌区)和 .app-shell__topbar(顶部状态栏)
  • CSS 改为 app-region: no-drag + user-select: none,避免与自定义拖动冲突
  • 新增 E2E 测试:验证拖动后窗口位置变化但尺寸不变

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • 新增/更新单元测试:BatchProcessor(reap exhausted + concurrency cap)、CaptureBatcher(idle slide + age cap)、TimelineWindowCoordinator(force seal + no spin + no reentrant advance)
  • IPC 契约测试更新:EXPECTED_INVOKE_CHANNEL_COUNT 82 → 83,MIN_VALIDATED_CHANNELS 40 → 41
  • 本地构建与 NSIS 安装包打包通过

v0.5.2 — 安全存储迁移 + 导航护栏加固 + 渲染层架构重构

Choose a tag to compare

@xscanzm xscanzm released this 26 Jul 06:47

v0.5.2 — 安全存储迁移 + 导航护栏加固 + 渲染层架构重构

本次版本聚焦安全加固与内部架构整理:API Key 存储从已归档的 keytar 原生模块迁移到 Electron 内置 safeStorage(DPAPI),降低原生依赖维护成本;BrowserWindow 新增导航白名单护栏,作为 prompt injection 逃逸的纵深防御;数据库写入增加 busy_timeout 与迁移备份自动清理;渲染层将单文件 global.css 与 store.ts 拆分为按页面/组件的 CSS 多文件与 Zustand slice 模式。

新增功能

1. 密钥存储迁移(keytar → Electron safeStorage)

将 API Key 从 keytar(Windows 凭据管理器原生模块)迁移到 Electron 内置 safeStorage,密文落盘到 %APPDATA%/Recall/data/secrets.json

  • 新增 src/main/services/secretsMigration.ts:启动时幂等执行一次迁移,写成功后才删源(避免 key 丢失),全程不记录 key 本身
  • keytar 模块缺失时静默跳过(keytar_unavailable),不挡启动;最坏情况老用户重填一次 API Key
  • 接入点:app.ts 启动早期、任何模型调用之前跑完
  • 迁移动机:keytar 上游已归档不再维护,每次 Electron 大版本升级都要 electron-rebuild;safeStorage 安全等级同源(都基于 DPAPI,按当前用户+机器绑定),密文刻意不进 SQLite 避免被备份/VACUUM INTO 复制时扩大暴露面
  • 新增 SecretService.test.ts / secretsMigration.test.ts:9 个用例覆盖正常迁移、写失败保留源、空密码、二次运行幂等、safeStorage 不可用、keytar 缺失等

2. 导航护栏(navigationGuard)

给 BrowserWindow 的 WebContents 安装导航白名单护栏,作为 prompt injection 防御的纵深第二层。renderer 渲染的内容包含不可信输入(OCR 屏幕文字 + 模型生成报告正文),即使模型被绕过吐出可点击链接或触发 window.open,主进程必须兜住:

  • 新增 src/main/services/navigationGuard.ts:覆盖四类逃逸面
    • setWindowOpenHandler:一律 deny,外部链接走 memory:openSourceUrl → shell.openExternal 正规通道
    • will-navigate:主框架导航白名单外 preventDefault
    • will-frame-navigate:子框架(iframe)导航白名单外 preventDefault
    • will-attach-webview:一律拒绝(本应用不使用 webview)
  • 白名单允许:dist/renderer 目录下的 file: 资源(含 query/hash)、开发模式 dev server 同源地址、启动占位页精确匹配的 data: URL
  • 显式拒绝:http(s) 外链、其他 data:/blob: URL、javascript:/ftp:/about:、renderer 目录外的 file: 路径(含 .. 穿越与同前缀兄弟目录攻击)
  • 应用到主窗口与 EndOfDayReview 日报弹窗
  • 新增 navigationGuard.test.ts:8 个用例覆盖目录内外导航、穿越、同前缀兄弟目录、生产模式拒绝 http/https、dev server 同源、data: URL 精确匹配等

改进

3. 数据库稳健性

  • Database.ts:启用 busy_timeout = 5000ms,避免并发写撞锁(BatchProcessor / TimelineWindowCoordinator / ReportScheduler / ProjectionInvalidationProcessor / ScreenshotCacheScheduler / 准入后台重评)立即抛 SQLITE_BUSY
  • 新增 pruneMigrationBackups:迁移成功后自动清理历史备份,只保留最近 2 份(MIGRATION_BACKUP_KEEP);备份文件名内嵌 ISO 时间戳,按名排序即按时间排序;清理失败不影响启动
  • 迁移失败时保留全部备份用于人工恢复

4. 渲染层架构重构(内部,用户无感)

将单文件 global.cssstore.ts 拆分为按职责分层的多文件结构,对外 API 与用户可见行为均保持不变:

  • CSS 拆分global.css 退化为纯 @import 清单,拆为基础层(base.css / pages-common.css / pages-normalize.css)、布局层(app-shell.css)、页面层(today.css / reminders.css / settings.css / projects.css / people.css / reports.css / memory-search.css / memory-detail.css / debug.css / trust-center.css / unfinished.css)、组件层(components.css / dialogs.css / today-*.css / reports-history.css / update-badge.css 等)、两轮精修(refinement.css / refinement-pass2.css
  • 状态管理拆分store.ts 精简为极薄组合器(约 30 行),按域拆为 8 个 slice(shell / today / reminders / search / objects / settings / reports / debug);领域类型抽到 state/types.ts,跨 slice 共用初始值与日期工具抽到 state/defaults.ts;对外仍是 useAppStore((s) => s.someAction),页面无需改 import

5. 工程基础设施

  • 新增 eslint.config.mjs:ESLint 9 扁平配置,主进程/共享层与渲染层走不同 tsconfig,仅开"真能抓 bug"的类型感知规则(no-floating-promises / no-misused-promises / await-thenable / no-explicit-any),no-console 限制为只允许 error/warn
  • 新增契约测试:settingsContract.test.ts(设置 IPC 契约)、migrations.contract.test.ts(迁移契约)
  • windows-ci.yml 调整:fast 轨道 lint + typecheck + test:coverage + Worker 独立测试,heavy 轨道 build + sqlite + smoke + e2e,concurrency cancel-in-progress
  • ActivityService.start() 首次轮询吞异常,避免未处理拒绝
  • MemorySearchRepository 删除未使用的 TYPE_LABELS,FTS 失败分支补注释

验证

  • TypeScript 主进程与渲染进程类型检查通过
  • 全部单元测试与契约测试通过
  • 本地构建与 NSIS 安装包打包通过