feat: v0.8.1 记忆深化 · 观测闭环 · 体验收口 — 窗口/输出上限全局单一配置 · 2478 用例全量回归 + E2E 冒烟
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m8s
CI / 全量测试 (Electron ABI) (push) Failing after 6m0s
CI / 产物编译验证 (push) Successful in 10m58s

硬性契约:删除代码中一切写死的上下文窗口与最大输出上限(含六家模型元信息
钳制与全部兜底值)——唯一合法来源是设置面板「上下文长度」(llm.contextWindow)
与「最大输出上限」(llm.maxTokens),跨 Provider/模型原样透传。

P0 正确性收口:
- 迁移 11/12(SCHEMA_VERSION 5):记忆表 embedding 列 + 分 Provider 窗口键清理
- 记忆生命周期接线:会话终态清理 working memory / episodic 90 天 TTL / access_count 回写
- 回放缓冲模块化 + 会话终态清理(杜绝 4MB/会话内存滞留)
- i18n 收口:主进程 main-locale(zh/en,ui.locale 热切换)+ 渲染层 17 处出层

P1 能力演进:
- 本地向量混合检索:0.6×向量余弦 + 0.4×TF-IDF,Ollama embeddings 首次投产,
  存量记忆惰性回填,嵌入不可用自动回退 TF-IDF
- MEMORY.md 维护闭环:固化去重消除截断盲区;两阶段维护(AI 建议 → 用户确认 →
  原子改写 + 语义记忆双轨同步 + 审计);>50KB 告警
- 可观测闭环:cacheTokens 引擎→前端透传(Token 面板命中率/成本行)+ 输入框
  上下文占用指示条
- MCP Prompts/Resources 对话可用:/mcp:{server}:{prompt} 与 @mcp:{server}:{uri}

P2 体验补全:
- 工具自定义策略(正则白/黑名单 + 频率 + 强制确认,热生效)
- 连续 ≥3 同类工具确认聚合为单弹框
- 会话消息游标分页(首屏 200 条向上翻页)
- 开机自启;Playwright + Electron E2E 冒烟(本地 mock LLM 零外联)

Review 回归修复:MCP 大小写失配 / 分页状态复位 / 清空=未配置语义(Number(null)=0
隐患)/ MEMORY.md 告警位置 / working_memories FK(迁移 13)/ 全局配置层废键清理;
附带根治权限加固启动时序、代理回环放行、safeStorage 降级、悬空 symlink 逃逸。

验证:typecheck/lint 0 问题;test:electron 2478/2478(0 跳过);E2E 2/2;
docs/v0.8.1-迭代实施清单.md 全项留档。
This commit is contained in:
2026-09-08 09:35:58 +08:00
parent 839860083f
commit 9b45c445bf
85 changed files with 5286 additions and 1158 deletions
+239
View File
@@ -0,0 +1,239 @@
# v0.8.1 迭代实施清单 —「记忆深化 · 观测闭环 · 体验收口 · 配置单一化」
> 版本基线:v0.8.08398600)→ 0.8.1
> 本文档是 v0.8.1 的**唯一实施与验收依据**。每项含:根因 / 根治方案 / 涉及文件 / 验收标准。
> 硬性契约(用户指令,贯穿全版本):**代码中不存在任何写死的上下文长度或最大输出上限;
> 唯一合法来源是设置面板 LLM 配置的「上下文长度」(llm.contextWindow)与「最大输出上限」
> llm.maxTokens),对一切 Provider / 模型生效,不做模型钳制。**
---
## P0 — 正确性收口
### P0-1 上下文窗口 / 最大输出上限 全局单一配置化(硬性契约,取代原"按模型真值修正")
- **根因**:① 六家 adapter 各自持有 MODEL_INFO 的 contextWindow / maxOutputTokens 写死值,
并按其钳制 max_tokensvision-exp 128K 与配置 1M 冲突时压缩阈值失真,先 413 再压缩);
② base-adapter / openai-compatible-base / engine / agent-store / 引导页均有写死兜底
128K / 4096 / 1M / 63488 / 2048);③ 分 Provider contextWindow 配置键与 ollama.numCtx
造成多源语义。
- **根治方案**
1. CONFIG_DEFAULTS 新增 `llm.contextWindow`(种子默认 131072)为唯一上下文窗口配置;
`llm.maxTokens`(63488)为唯一输出上限配置;删除 deepseek/agnes/mimo/openai/
anthropic.contextWindow 与 ollama.numCtx 五个种子键。
2. 迁移 11SCHEMA_VERSION 3→4):episodic/semantic_memories 增 embedding BLOB 列(P1-1);
迁移 12:DELETE 存量库中已废除的六个配置键。
3. 全部 adapter 的 MODEL_INFO 删除窗口/上限数值字段;deepseek/agnes/mimo/openai 的
max_tokens / max_completion_tokens / num_predict 原样透传 `params.maxTokens`(未配置
不下发);anthropic 删除模型钳制,仅保留 thinking 协议下限 2048(协议不变量,
非输出上限);ollama 删除 num_ctx 探测缓存与 4096 默认。
4. getContextWindow 契约单一化:config(设置面板值)> 0 返回之,否则返回 0
(引擎 syncContextWindow 仅采纳 >0;压缩预算 effectiveContextWindow<=0 时跳过压缩)。
5. engine DEFAULT_CONFIG 删除 contextWindow/maxTokensAgentLoopConfig 二字段改可选;
SubAgentorchestrator)不再携带 63488/128_000 兜底。
6. main.ts / shared.tsLLM_CONFIG_KEYS、applyEngineConfigKey):
`llm.contextWindow` 全局键驱动 adapter 重建 + 引擎 contextWindow 热更新,
Ollama 场景同步 contextLengthnum_ctx);分 Provider 键进入兼容分支仅 WARN。
7. LLMSettings 重写:全局唯一「上下文长度」「最大输出上限」两个输入项;
删除 supportsOutputLimitConfig 门控、模型元信息钳制提示与 numCtx 分字段;
OnboardingWizard 删除 DEFAULT_CTX 写死表,写 `llm.contextWindow`
8. agent-store 删除 4096/1M 常量;setProvider 读 `llm.contextWindow`;新增
setContextWindow 热更新(config:changed 联动)。
- **涉及文件**database.service / main / ipc/shared / ipc/agent(无)/ engine / agent-loop
types / 六家 adapter / base-adapter / openai-compatible-base / agent-store / useAgentStream /
LLMSettings / OnboardingWizard / model-capabilities / 相关测试
- **验收**typecheck 三绿;max-tokens-clamp 契约测试重写为"原样透传 + 未配置不下发 +
anthropic thinking 协议下限"provider-request-shapes 钳制矩阵全部改为透传矩阵;
E2E 冒烟断言请求体 max_tokens === 设置值(2048)。
### P0-2 记忆生命周期接线(死账清理)
- **根因**`clearWorkingMemory` 零调用方(working_memories 随会话永久残留);
`episodic_memories.expires_at` 无写入方(cleanupExpired 空转);semantic
`access_count` 只读不写(LRU 淘汰死语义)。
- **根治方案**:会话终态(sessions:delete / purge / clearMessages、data:clearSessions、
SubAgent 终态 finishSubTrace 与 abort 路径)调用 `memoryManager.clearWorkingMemory`
MemoryTriggerHook 写 episodic 时携带 90 天 TTL`search()` 命中后回写 access_count。
- **涉及文件**memory/manager、hooks/post-tool、ipc/sessions、ipc/agent、ipc/data
- **验收**memory-manager 测试新增 expiresAt 写入 / access_count 递增用例(Electron ABI)。
### P0-3 回放缓冲终态清理
- **根因**v0.8.0 引入的 replayBuffers 为 ipc/agent.ts 内部 Map,会话删除/彻底删除
无联动清理,已删会话最多 4MB/会话滞留(仅 LRU 50 兜底)。
- **根治方案**:抽为 `electron/ipc/replay-buffer.ts` 独立模块(append/reset/mark/clear/get);
sessions:delete / purge 调用 clearReplayBuffer。
- **涉及文件**ipc/replay-buffer(新)、ipc/agent、ipc/sessions
- **验收**replay-buffer.test 六用例(有界、truncated、runId、TERMINATED 保留、INIT 重置、
终态清除)全绿。
### P0-4 i18n 收口第二期
- **根因**:主进程 toast/系统通知(压缩、死循环、故障转移、固化、sendMessage 错误路径、
更新/完成通知)与渲染层 14 处 toast 硬编码中文。
- **根治方案**:新增 `electron/utils/main-locale.ts`ui.locale 驱动、mt(key,params) 取词、
zh/en 双表、启动注入 + applyConfigSideEffects 热切换);ipc/agent.ts、main.ts、ipc/shared.ts
全部文案出层;渲染层 agent-store / App / useKeyboardShortcuts / UserMessage / useAgentStream
(Provider 切换、输出验证)出层并入 i18n-stringszh/en)。
- **涉及文件**utils/main-locale(新)、ipc/agent、ipc/shared、main、agent-store、App、
useKeyboardShortcuts、UserMessage、useAgentStream、i18n-strings
- **验收**`grep` 断言渲染层 toast 调用零硬编码中文;typecheck/lint 绿。
## P1 — 能力演进
### P1-1 本地向量混合检索(激活 embed 死代码)
- **根因**OllamaAdapter.embed() 全项目零调用;TF-IDF bigram 对同义改写零召回。
- **根治方案**`memory/embedder.ts` 契约 + main.ts 装配(仅 Ollama Provider 且用户配置
`memory.embeddingModel` 时启用,adapter 闭包动态读取);迁移 11 embedding BLOB 列;
store 写入后异步回填向量(in-flight 去重);search 升级 async:混合评分 =
0.6×向量余弦 + 0.4×TF-IDF(各自叠加时间衰减与重要度;单路缺失回退单路);
**存量记忆惰性回填**:检索候选中 embedding 为 NULL 的行排队补算(本轮仍走 TF-IDF,
后续查询命中向量路径,无独立迁移任务、嵌入器不可用零开销);AgentSettings 增
「向量嵌入模型」配置(空 = 关闭)。
- **涉及文件**memory/embedder(新)、memory/manager、main、ipc/memory、AgentSettings、
i18n-strings、database.service(迁移 11
- **验收**memory 套件 86 用例绿(含 5 个新增:TTL 写入、access_count、同义改写混合命中、
embedder 抛错/返回 null 回退、异步回填 BLOB)。
### P1-2 MEMORY.md 维护闭环
- **根因**:固化 append-only;固化 prompt 全文截 3000 字符 → 尾部条目对 LLM 不可见,
去重失效重复写入;无任何回收路径,MEMORY.md 无限膨胀。
- **根治方案**:① digest 共享化 —— `parseMemoryEntries` / `buildMemoryEntriesDigest`
(纯条目行、8000 字符预算)同时用于固化去重与维护分析;② `memory/maintainer.ts`
两阶段维护:analyzeLLM 产出 delete/update 建议,条目精确匹配校验防幻觉)→
apply(用户勾选确认后:重写 MEMORY.md —— workspaceService.rewriteMemory 原子写、
同步 semantic_memories、审计留痕);③ IPC `memory:analyzeMaintenance` /
`memory:applyMaintenance`(结构校验 + 审计);④ MemoryViewer「整理记忆」入口 +
建议勾选弹框;⑤ MEMORY.md > 50KB 固化后 WARN 提示。
- **涉及文件**memory/maintainer(新)、memory/consolidator、services/workspace、ipc/memory、
ipc/context、main、preload、global.d.ts、MemoryViewer、ipc/agent(超限告警)、i18n-strings
- **验收**maintainer.test 五用例(解析、digest 无盲区、精确匹配防幻觉、update 双轨同步、
动作数上限)+ consolidator 套件回归全绿。
### P1-3 上下文 / 成本可观测闭环
- **根因**adapter 采集的 cacheHitTokens/cacheMissTokens 在引擎 USAGE 映射与前端
TokenUsage 类型被丢弃;ChatInput 无上下文占用指示(UI/UX 文档预留)。
- **根治方案**engine TokenUsage 增缓存字段并随 USAGE/accumulate 透传;agent-store
TokenUsage 扩展 + useAgentStream 累加(Provider 不上报保持 undefined);TokenUsage
面板新增「Prompt 缓存命中」行与「估算成本」行(单价 llm.priceInput/llm.priceOutput
为设置面板可选配置,未配置隐藏 —— 成本无任何写死价格);ChatInput 顶部上下文占用
指示条(lastInputTokens / llm.contextWindow60%/80% 变色)。
- **涉及文件**agent-loop/types、engine、agent-store、useAgentStream、TokenUsage、ChatInput、
LLMSettings(单价输入)、i18n-strings
- **验收**typecheck/web 绿;E2E 冒烟在真实渲染界面运行(contextWindow/maxTokens 种子值
驱动)。
### P1-4 MCP Prompts / Resources 可用化
- **根因**v0.8.0 仅"发现与列表"prompts/resources 在对话中不可用。
- **根治方案**mcp-manager 增 getPromptprompts/get,单 ContentBlock 文本提取)/
readResourceresources/read,二进制 blob 显式拒绝);IPC `mcp:getPrompt` /
`mcp:readResource`512KB 截断);ChatInput:斜杠菜单动态合并 MCP prompts
`/mcp:{server}:{prompt}` → getPrompt 填充输入框);@ 提及合并 MCP resources
`@mcp:{server}:{uri}` → readResource 注入附件管线,token 集合扩展 ':');
server 不支持时菜单自然不出现。
- **涉及文件**mcp-manager、ipc/mcp、preload、global.d.ts、ChatInput、i18n-strings、
mcp-contents.test(新)
- **验收**mcp-contents.test 五用例(getPrompt 展平 / 未连接抛错 / text 返回 / blob 拒绝 /
空 contents null+ ChatInput typecheck。
## P2 — 体验补全
### P2-1 工具自定义策略(UI/UX 文档预留项落地)
- **根治方案**permissions.ts 增 `parseToolPolicy`(JSON 配置 → 正则编译,非法正则跳过、
fail-closed)与 `setPolicyOverride/getPolicyOverride`(覆盖层优先于默认策略,字段级合并
保留默认安全项);存储键 `tools.{name}.policy`main.ts 启动冷加载 + shared.ts
config:set 热加载(IPCContext 增 policyEngine);ToolsSettings 每工具「策略」编辑器
(拒绝/允许正则、频率上限、强制确认,inline 非法正则提示)。
- **涉及文件**sandbox/permissions、ipc/context、ipc/shared、main、ToolsSettings、i18n-strings
- **验收**typecheck/lint 绿;覆盖层优先级由 resolvePolicy 单一解析点保证。
### P2-2 连续同类工具批量确认聚合(UI/UX 文档预留项落地)
- **根治方案**ConfirmationHook 聚合窗口(800ms)—— 同 (session, tool) 并行请求达
3 条即 flush 为单条 `tool:confirmationRequestBatch` 广播(低于阈值逐条广播,原行为);
clearPending 同步丢弃未广播缓冲;preload/global.d.ts 增批量监听;ConfirmationDialog
消费批量事件(倒计时取最早 expiresAt)。
- **涉及文件**confirmation-hook、preload、global.d.ts、ConfirmationDialog、
confirmation-hook.test(广播测试改 fake timers + 新增 2 用例)
- **验收**hooks 套件 48 用例绿(≥3 聚合单事件、<2 逐条)。
### P2-3 会话消息游标分页加载
- **根因**sessions:getMessages 全量加载无上限,超长会话切换 IPC 载荷大。
- **根治方案**session.service 增游标语义(无 limit 全量兼容;limit 无游标 = 尾部窗口;
limit+beforeRowid = 游标前 N 条,统一升序);IPC 参数校验;preload/global.d.ts 类型;
agent-store 首屏尾部窗口(200 条)+ loadOlderMessages(防重入 + 完整性判定 +
竞态保护)+ ChatMessage.rowIdMessageList startReached 触发向上加载。
- **涉及文件**session.service、ipc/sessions、preload、global.d.ts、agent-store、
MessageList、session-pagination.test(新)
- **验收**pagination.test 四用例(全量兼容 / 尾部窗口 / 游标翻页 / 开头完整性)绿。
### P2-4 开机自启
- **根治方案**IPC `app:setLoginItem` / `app:getLoginItem`app.setLoginItemSettings +
读回真实生效状态,Linux 不可用平台 fail-safe);AppearanceSettings 增开关
(乐观更新 + 读回校正 + 不支持平台提示);preload / global.d.ts 接线。
- **涉及文件**ipc/app、preload、global.d.ts、AppearanceSettings、i18n-strings
- **验收**typecheck 绿;读回语义保证 UI 与平台真实状态一致。
### P2-5 E2E 冒烟(Playwright + Electron
- **根治方案**`e2e/mock-llm.ts`(本地 OpenAI 兼容 SSE Provider,随机端口,零外联);
`e2e/metona.spec.ts`(隔离 userData + 种子确定性合法 LLM 配置 → 发消息 → 断言流式
回复渲染 + 请求体携带设置 maxTokens);playwright.configmain.ts 增
`METONA_USER_DATA_DIR` / `METONA_E2E_SEED_CONFIG` 引导钩子(显式 env 才生效);
npm script `test:e2e`build + playwright)。
**附带根治三项环境级缺陷**:① 权限白名单在 app ready 前注册抛异常静默失效
(延迟到 ready 后,防线真正生效);② 代理 dispatcher 不放行回环目标(Loopback
Bypass 组合 dispatcher,本地 Ollama/SearXNG/E2E 全部修复);③ safeStorage 加密
回读失败无降级(roundtrip probe,失败会话降级明文存储)。
- **涉及文件**e2e/*(新)、playwright.config(新)、main、utils/network-proxy、
utils/secure-config、package.json
- **验收**`npx playwright test` 2/2 绿(本机含代理环境实测通过)。
## P3 — 收尾
- package.json → 0.8.1README 徽章 / 亮点表 / 配置说明 / 测试命令对齐;
- IR 标准文档补 usage.cacheTokens 透传与"输出上限无模型钳制"语义;
- 全量 typecheck + lint + `npm test`(系统 Node+ `npm run test:electron`(全量)。
---
## Review 回归修复(2026-09-08 第二轮)
完整回归 review(配对审计 + 高风险 diff 逐行复核)发现并修复:
| # | 类型 | 内容 |
|---|---|---|
| R1 | 真 bug | **MCP Prompt 斜杠命令大小写失配** —— cmd 被整体 toLowerCase,大写 server 名与 MCPManager 原始名 Map key 失配("not connected");改为对 mcpPrompts 清单大小写不敏感匹配反查原始名 |
| R2 | 状态一致性 | **分页状态复位缺失** —— agent-store 的 clearMessages / resetSessionState 未复位 messagesComplete/loadingOlder;已补 |
| F1 | 观察项 O1 | **LLMSettings 清空输入静默回写种子默认值**63488/131072+ shared.ts `Number(null)=0` 会把引擎预算清零 —— 语义改为"清空 = 写入 null(未配置)":引擎跳过压缩预算 / 输出上限参数不下发(由服务端默认值决定),helper 文案同步 |
| F2 | 观察项 O2 | **维护弹框空分区提示** —— analyze proposal 增加 sectionEntryCounts,弹框计算"应用后变空的分区"并向用户提示(分区头保留) |
| F3 | 告警位置 | **MEMORY.md >50KB 告警移出固化分支** —— 此前仅固化触发时检查,跳过固化的大文件永不告警;改为每次成功 run 后检查 + 每会话一次去重 |
| F4 | 历史 schema | **working_memories 缺 sessions 外键**(v0.2.0 建表起缺失级联)—— 迁移 13SCHEMA_VERSION 5):孤儿行清理 + 带 FK(CASCADE) 重建;createTables 新库口径对齐 |
| F5 | 历史残留 | **全局配置层废键未清理** —— GLOBAL_KEY_PREFIXES 移除五个 provider 前缀与 ollama.(其下唯一键已废除);initialize 清除全局 JSON 中的 DEPRECATED_CONFIG_KEYS,与工作空间迁移 12 对齐 |
**验证**typecheck 0 错误 / lint 0 问题 / test:electron **2478/2478**(净增 sectionEntryCounts 用例)/ E2E 2/2。
---
## 验证记录(实施完成后回填)
> 全局验证(2026-09-07):
> - `npm run typecheck` 0 错误;`npm run lint` 0 问题
> - `npx playwright test` **2/2 通过**E2E 冒烟)
> - `npm test`(系统 Node):2167 通过 / 310 按 ABI 设计跳过
> - `npm run test:electron`Electron ABI 全量):**2478/2478 通过,0 跳过**
> (基线 2445 → 0.8.1 净增 33 个用例:向量混合检索 / 维护闭环 / 回放缓冲 /
> 分页 / 批量确认聚合 / MCP contents / secure-config probe / sectionEntryCounts
> - sandbox 符号链接用例曾在本机暴露悬空链接绕过 realpath 的真实缺陷(P0 级),
> 已随 0.8.1 根治并通过
| 项 | 验证方式 | 结果 |
|---|---|---|
| P0-1 配置单一化 | max-tokens-clamp 透传契约 12 用例;provider-request-shapes 透传矩阵 19 处重写;anthropic/openai/ollama getContextWindow 0 回退契约;迁移 12 清理键;E2E 断言 max_tokens=2048 透传;附带根治:权限白名单 ready 时序 / 代理回环放行 / safeStorage roundtrip 降级 / 悬空 symlink 白名单逃逸(sandbox lstat | ✅ |
| P0-2 记忆生命周期 | memory-manager 新增 expiresAt / access_count 用例;sessions/data/agent 终态接线(ipc 测试 stub 同步) | ✅ |
| P0-3 回放缓冲 | replay-buffer.test 六用例 + sessions:delete/purge 联动 | ✅ |
| P0-4 i18n 收口 | main-locale zh/en 双表 + 热切换;渲染层 17 处出层;grep 断言零残留 | ✅ |
| P1-1 向量混合检索 | memory 套件 86 用例(含同义改写命中 / 回退 / 惰性回填 / BLOB 回填) | ✅ |
| P1-2 MEMORY.md 维护 | maintainer.test 五用例 + 固化 digest 共享 + MemoryViewer 弹框 + 审计 | ✅ |
| P1-3 可观测闭环 | engine/agent-store cache 字段透传 + TokenUsage 命中率/成本行 + ChatInput 指示条 | ✅ |
| P1-4 MCP 可用化 | mcp-contents.test 五用例 + ChatInput 斜杠/@mcp 接线 | ✅ |
| P2-1 工具自定义策略 | parseToolPolicy fail-closed 解析 + setPolicyOverride 优先级 + ToolsSettings 编辑器 | ✅ |
| P2-2 批量确认聚合 | confirmation-hook.test +2(≥3 聚合 / <3 逐条),既有广播测试 fake timers 适配 | ✅ |
| P2-3 游标分页 | session-pagination.test 四用例 + store/MessageList startReached 接线 | ✅ |
| P2-4 开机自启 | setLoginItem/getLoginItem + 读回语义 + AppearanceSettings 开关 | ✅ |
| P2-5 E2E 冒烟 | playwright 2/2;附带根治权限加固时序 / 回环代理放行 / safeStorage 降级 | ✅ |
| 收尾 | package.json 0.8.1README / IR 标准 / 本清单入库;全量三绿 | ✅ |