Files
metona-ai-desktop/docs/v0.8.1-迭代实施清单.md
T
thzxx 9b45c445bf
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m8s
CI / 全量测试 (Electron ABI) (push) Failing after 6m0s
CI / 产物编译验证 (push) Successful in 10m58s
feat: v0.8.1 记忆深化 · 观测闭环 · 体验收口 — 窗口/输出上限全局单一配置 · 2478 用例全量回归 + E2E 冒烟
硬性契约:删除代码中一切写死的上下文窗口与最大输出上限(含六家模型元信息
钳制与全部兜底值)——唯一合法来源是设置面板「上下文长度」(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 全项留档。
2026-09-08 09:35:58 +08:00

240 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 标准 / 本清单入库;全量三绿 | ✅ |