Files
metona-ai-desktop/docs/v0.8.0-迭代实施清单.md
T
thzxx 839860083f
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m8s
CI / 全量测试 (Electron ABI) (push) Failing after 6m8s
CI / 产物编译验证 (push) Successful in 11m1s
fix: v0.8.0 修订 — 思考用户意图优先 · 移除元信息硬门控 · 实施清单入库
- DeepSeek/MiMo/Agnes 移除 supportsThinking 元信息硬门控:思考参数完全遵循用户配置
  (事故复盘中 vision-exp 元信息标注不支持思考、实际产生了 8189 token 推理内容,
  元信息不可靠;预算耗尽由引擎降级重试兜底,元信息不符仅告警不拦截)
- Ollama 保留 /api/show 能力探测门控(服务端硬协议约束:向不支持思考的模型发
  think 每次请求 400,属协议正确性而非意图覆盖),探测失败 fail-open
- LLM 设置提示文案修订:元信息不符仍按用户配置发送,降级重试自动兜底
- 测试契约反向钉住:vision-exp + 用户开启→照发 enabled+reasoning_effort;
  关闭/未配置→显式 disabled;Ollama 探测 false→不发 think / null→fail-open
- 补录 docs/v0.8.0-迭代实施清单.md(含逐项验证记录与本次修订记录;
  首次提交时该文件因故未入库,本次补齐)
- 验证:typecheck 0 错误 / lint 0 问题 / 系统 Node 2146 通过 / thinking 矩阵 101 用例全绿
2026-09-05 20:49:20 +08:00

209 lines
29 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.0 迭代实施清单 —「流语义补全 · 会话可靠 · 恢复力」
> 版本基线:v0.7.499d0c54)→ 提交 5b9d4d1 + 修订提交
> 本文档是 v0.8.0 的**唯一实施与验收依据**。每项含:根因 / 根治方案 / 涉及文件 / 验收标准。
> 实施完成后,逐项在「验证记录」章节回填验证方式与结果。
---
## P0 — 会话可靠性收口(根治"模型思考中会话停止")
### P0-1 finish_reason 全链路贯通
- **根因**`finish_reason=length``sse-stream.ts` 仅打日志(engine.ts:340 空输出被判定为 COMPLETED 的上游盲区);Anthropic 流式 `message_delta.delta.stop_reason`anthropic.adapter.ts:241)、Ollama 流式 `done_reason` 均被忽略;TRACE 层 `recordLLMResponse` 的 finishReason 硬编码 `'stop'`ipc/agent.ts 两处),事故后无法归因。
- **根治方案**
1. IR 扩展:`MetonaStreamEvent` 增加可选 `finishReason?: string`DONE 事件携带);`IterationStep` 增加 `finishReason?: string`agent-loop/types.ts)。
2. OpenAI 兼容共享 SSE 层(sse-stream.ts):跟踪每帧 `choices[0].finish_reason`,在真实 `[DONE]` 与断流合成的 DONE 事件上携带;`length` 保留日志但不再只是日志。
3. Anthropic`message_delta` 采集 `delta.stop_reason`,映射(max_tokens→length / refusal|content_filter→content_filter / end_turn→stop / tool_use→tool_calls / pause_turn→stop),携带到 DONE(真实 message_stop 与断流合成两路)。
4. Ollama`chunk.done` 时采集 `done_reason`stop/length/load/unload 映射),携带到 DONE。
5. 引擎:消费 adapter DONE 前捕获 finishReason 写入 `step.finishReason`;引擎最终 DONE 事件携带最后一轮 finishReason。
6. TRACEipc/agent.ts 主管道与 SubAgent 管道记录真实 finishReason(从 DONE 事件取,缺省 'stop')。
- **涉及文件**`metona-response.ts``agent-loop/types.ts``shared/sse-stream.ts``anthropic.adapter.ts``ollama.adapter.ts``engine.ts``ipc/agent.ts`
- **验收**:构造 finish_reason=length 的 SSE 流 → 引擎 step.finishReason==='length'、最终 DONE 携带 finishReason、TRACE JSONL 的 llm_response.finishReason==='length'。
### P0-2 引擎空响应守卫 + 截断自愈(degraded retry
- **根因**:引擎对流结束只有二元判定(有工具调用→继续,无→COMPLETED),零内容/思考耗尽型截断全部静默 COMPLETED(生产事故实锤:main.log 22:38:56 / 22:42:35deepseek-v4-flash-vision-expoutput=8192,正文为空,completed 收尾)。
- **根治方案**(收敛在 `chatStreamWithRetry` 内,天然获得重试/故障转移通道):
1. 生成器内跟踪:`sawThought`REASONING_DELTA)、`sawOutput`TEXT_DELTA / TOOL_CALL_DELTA / TOOL_CALL_COMPLETE)、`lastFinishReason`DONE 携带)。
2. 零内容流(`!sawThought && !sawOutput`)→ 抛 `EmptyResponseError`(可重试,status=503)→ 指数退避重试;重试耗尽 → ERROR 收尾,message 明确"模型未返回任何内容(可能被输出上限截断或上游异常)"。
3. 思考耗尽型截断(`sawThought && !sawOutput && finishReason==='length'`)→ **一次性降级重试**:临时以 `thinkingEnabled=false` 重发本次请求(先发 RETRY/STREAM_RESET 事件清前端缓冲),每 run 仅一次;降级重试仍失败 → 抛 `OutputTruncatedError`(不可重试,code=OUTPUT_LENGTH_EXCEEDED)→ 走故障转移(备用 Provider 预算更大即自愈)→ 无备用则 ERROR 收尾并给用户明确文案(建议提高输出上限 / 关闭思考 / 更换模型)。
4. 带正文/带工具参数的 length 截断:不重试(工具参数截断已有 `_truncatedArguments` 自愈),引擎 COMPLETED 但 DONE 携带 finishReason='length',前端展示截断提示。
5. `finish()` 的 ERROR/DEAD_LOOP 事件 code 透传:识别 `OutputTruncatedError``MetonaErrorCode.OUTPUT_LENGTH_EXCEEDED`
- **涉及文件**`engine.ts`chatStreamWithRetry / finish / isRetryableError)、渲染层(见 P0-4
- **验收**:① 零事件流 mock → 重试 3 次后 ERROR,事件含明确 message;② reasoning-only+length → 降级重试一次(第二次请求体无 thinking 字段)→ 成功则 COMPLETED;③ 降级后仍 length → OUTPUT_LENGTH_EXCEEDED 错误事件;④ 有正文 + length → COMPLETED 且 DONE.finishReason==='length'。
- **附带根治**:abort 恰逢零工具调用轮被 COMPLETED 分支抢占(USER_INTERRUPT 被静默吞掉)——主循环前置 abort 优先级检查;orchestrator 测试"被中断 SubAgent success=false"契约更新。
### P0-3 思考参数与输出预算对齐(修订版:用户意图优先)
- **初版方案(已被修订推翻)**:按 MODEL_INFO.supportsThinking 元信息硬门控——supportsThinking===false 的模型强制不发思考参数。
- **修订原因**:事故复盘证明元信息不可靠——`deepseek-v4-flash-vision-exp` 元信息标注"不支持思考",实际却产生了 8189 token 推理内容;且引擎已有完整兜底链(**最大输出上限配置 → 空响应守卫 → 降级重试 → OUTPUT_LENGTH_EXCEEDED 明确报错**),继续硬拦截属替用户做决定、剥夺用户意图。
- **修订方案**
1. DeepSeek / MiMo / Agnes 三家:思考参数**完全遵循用户配置**(`thinkingEnabled` / `thinkingEffort`),元信息不符时仅 WARN 日志不拦截;未配置/关闭时仍显式 disabled(确定性契约,不依赖服务端隐式默认)。
2. 小输出预算告警保留:思考开启且钳制后 max_tokens < 8192 时 WARN 日志。
3. 设置页提示文案修订为:"当前模型元信息标注不支持思考:仍按你的配置发送思考参数;若思考耗尽输出预算,会自动关闭思考重试一次"。
4. **唯一保留门控是 Ollama**/api/show capabilities 探测(服务端实时真值,非静态元信息)为不支持思考时不发 think 参数——与云端不同,这是 Ollama 服务端的**硬协议约束**(向无思考能力的模型发 think 每次请求 400 "does not support thinking"),属协议正确性而非用户意图覆盖;探测失败 fail-open。
- **涉及文件**`deepseek.adapter.ts``mimo.adapter.ts``agnes-ai.adapter.ts``ollama.adapter.ts``src/components/settings/LLMSettings.tsx``src/lib/i18n-strings.ts`
- **验收**:用户开启思考时四家请求体均按用户配置携带思考参数(vision-exp 也照发 enabled + reasoning_effort);用户关闭/未配置→显式 disabledOllama 探测 false→不发 think。
### P0-4 渲染层截断/空响应可见性
- **根治方案**`useAgentStream` done 分支——`completed && finishReason==='length'` → system 消息"回答可能因输出上限被截断";error 分支对 `output_length_exceeded` 显示友好文案(i18n);`completed` 且最后一张 assistant 卡片完全为空(无 content/reasoning/toolCalls)→ system 消息"模型未返回内容,请重试或调整输出上限/思考设置"。文案全部入 i18n-stringszh/en)。
- **涉及文件**`src/hooks/useAgentStream.ts``src/lib/i18n-strings.ts``src/types/global.d.ts`
- **验收**:组件/hook 级测试覆盖三类提示。
### P0-5 回归测试四件套(+补充)
1. reasoning-only 流 + length 的引擎终止判定(本次事故形态,原零覆盖)。
2. `parseSSEStream` 集成级空闲超时:挂死 ReadableStream → SseUpstreamError(504)。
3. `SseUpstreamError(504)` 经引擎 `isRetryableError` 归类 → 重试路径一致性。
4. REASONING_DELTA 持续输出中 abort → USER_INTERRUPT(含迟到 reasoning 不写入)。
5. 补:engine P4-2(上一 run 30s 强制 abort + 5s 再等待)路径测试;降级重试请求体断言;DONE.finishReason 贯通断言;TRACE finishReason 真值断言。
- **涉及文件**`engine.test.ts``engine-reliability.test.ts``stream-error-and-truncation.test.ts``sse-stream.test.ts``provider-request-shapes.test.ts``ipc/__tests__/agent.test.ts`
- **验收**:新增用例全部通过,`npm run test:electron` 全量绿。
### FEAT-1 LLM 设置新增「最大输出上限」配置项(用户需求)
- **方案**
1. `LLMSettings` 新增「最大输出上限 (tokens)」数字项,读写既有配置键 `llm.maxTokens`(引擎/主进程已全链路消费:main.ts baseConfig → engine params.maxTokens → adapter 按 `{model}.maxOutputTokens` 钳制)。
2. **可见性门控**:新增渲染层 provider 能力映射(`model-capabilities.ts` 导出 `PROVIDER_OUTPUT_LIMIT_SUPPORT`,六家当前均支持输出上限参数——DeepSeek max_tokens / Agnes max_tokens / MiMo max_completion_tokens / OpenAI max_tokens|max_completion_tokens / Anthropic max_tokens / Ollama num_predict;映射表数据驱动,未来不支持者自动隐藏)。
3. 应用增强(根治静默钳制):输入旁展示当前模型 `maxOutputTokens` 上限提示;保存时配置值 > 当前模型 maxOutputTokenslistModels 元数据),toast 提示"将按模型上限 X 生效"`llm.maxTokens` 已在 `applyEngineConfigKey`(shared.ts)热生效,无需重启。
- **涉及文件**`src/components/settings/LLMSettings.tsx``src/lib/model-capabilities.ts``src/__tests__/model-capabilities.test.ts`
- **验收**:切 Provider 显示/隐藏正确(含不支持路径的模拟);保存后 `config:get llm.maxTokens` 值正确且引擎热更新被调用。
---
## P1 — 修复面收口
### P1-1 渲染层流式三缺陷
1. **后台会话流恢复(根治"切走再切回,回复消失一半")**:主进程 ipc/agent.ts 为每个运行中会话维护**有界回放缓冲**streamEvent + stateChange 统一序,上限 2000 条 / 4MBTERMINATED 后保留至下次 run 启动清除);新增 IPC `agent:getReplayState(sessionId)` 返回 `{isRunning, runId, events}`;渲染层新建 `stream-event-bus.ts`(订阅/发布),useAgentStream 的事件处理改为总线消费;`agent-store.setCurrentSession` 检测目标会话在 `sessionRunStates` 中运行 → 拉取回放并按序灌入总线 → 后续实时事件无缝衔接。缓冲溢出丢弃最旧并打标(replay truncated 提示)。
2. **abort() 自愈修复(根治"UI idle 但引擎仍在跑"**`useAgentStream` stateChange 分支——`!isStreaming && data.runId === store.currentRunId && data.state ∈ 活动态``setStreaming(true)` 恢复流式态;`agent-store.abort()` 的 IPC 失败路径:回滚本地状态(isStreaming=true、status=thinking+ toast 告知中断未生效;修正失实注释。
3. **sendMessage 收尾兜底**invoke resolve 后 2s 检查——若 isStreaming 仍为 true(说明 DONE/ERROR 收尾事件未达)→ 插入 system 消息"会话已结束但未收到正常收尾事件,已恢复输入"并 finalizeinvoke resolve 且 `r.success===false` 时若仍在流式 → 立即以 error 文案 finalize。
4. **abort 后悬空 executing 卡片兜底**done/error 处理时清扫所有 `status==='executing'|'pending'` 的工具调用(messages 与 traceSteps)→ 置 `error` + "已中断"。
- **涉及文件**`ipc/agent.ts``preload.ts``global.d.ts``src/lib/stream-event-bus.ts`(新)、`src/hooks/useAgentStream.ts``src/stores/agent-store.ts`
- **验收**:三场景 hook 级测试(切回恢复/abort 失败自愈/收尾兜底)+ ipc 回放缓冲单测。
### P1-2 工具 abort 信号全覆盖
- `http-request.ts``context.signal` 传入 `ssrfPinnedFetch` 第 4 参(管道已支持)。
- `web-fetch.ts`:HTTP 阶段与重定向链全程携带 signal;浏览器回退阶段 abort → `webContents.stop()` + reject。
- `web-search.ts`:预检 HEAD、SearXNG 请求、autoFetch 内部 web_fetch 全部携带真实 context.signal(移除假 context);autoFetch 加**时间预算**——总耗时不超过 `min(工具超时×0.8, 剩余预算)`,超预算即止(根治 720s>300s)。
- `code-search.ts`ripgrep 子进程接收 abort(监听 signal → child.kill())。
- `git.ts`:共享 execFile 帮助函数接收 `signal` 选项(Node execFile 原生支持)。
- `delegate-task.ts` + `orchestrator.ts`delegate 接受 `signal`signal aborted → `subEngine.abort()`
- **验收**:每工具至少一条"abort 后 X 毫秒内终止"的测试(子进程 kill / fetch reject / 委派终止)。
### P1-3 确定缺陷清单(根治修复)
1. **SSE 空 error 对象防御失效**extractUpstreamErrorFramemessage 兜底 `'{}'` 恒真)→ 无 message 且无 status 且无 code 时返回 null;同步修改锁行为测试(sse-stream.test.ts:589-596)。
2. **Ollama generate/embed 固定超时不合并外部信号** → 增加可选 `signal` 参数,`AbortSignal.any` 合并;pullModel 已有 signal 保持。
3. **isBinaryFile 拒绝一切 UTF-16** → BOMFF FE / FE FF)前缀视为文本(打通不可达的 UTF-16 解码分支);同步修改 filesystem-tools.test.ts:251-267 锁行为测试。
4. **tmp 文件名同毫秒碰撞**filesystem.ts / file-editor.ts)→ `tmp_${Date.now()}_${nanoid(6)}`
5. **code_search JS 回退参数不对称** → 回退实现补 `case_sensitive` / `context_before` / `context_after`
6. **list_directory 硬跳过 node_modules** → 新增可选参数 `include_node_modules`(默认 false,行为兼容)。
7. **window-manager 崩溃自愈无退避** → 连续 reload 计数:60s 内 ≥3 次不再自动 reload,改弹 error 对话框 + 日志;稳定 5 分钟后计数复位。
8. **i18n 收口**MemoryViewer "暂无记忆数据" → `t('memory.empty')`Sidebar ToolManagerPanel riskLabels 补 `critical`
- **验收**:每项一条回归测试(i18n 项组件测试断言)。
### P1-4 浏览器通道 SSRF 根治(本地 Pinned CONNECT 代理)
- **根因**web_fetch 浏览器回退与 web_browser.open 走 Chromium 自有网络栈,仅入口一次 SSRF 校验,无 DNS pinning。
- **根治方案**:新增 `electron/utils/pinned-proxy.ts`——主进程本地 HTTP 代理(127.0.0.1 随机端口):CONNECT → 解析目标 host → `resolvePublicAddresses` 校验(仅公网)→ 取 pinned IP 直接 `net.connect` 建立隧道(TLS/SNI/证书校验仍由 Chromium 端到端完成);绝对形式 HTTP 同校验后转发;校验失败 → 403 拒绝(fail-closed)。`BrowserWindowManager` 创建分区时按需启动代理并 setProxy;用户已配置 `network.proxyUrl`proxyActive)时保持既有行为(M7 已知限制记录在案)。失败降级:代理启动失败仅 WARN,浏览器回退可用性优先。
- **涉及文件**`electron/utils/pinned-proxy.ts`(新)、`browser-window-manager.ts``web-fetch.ts`(浏览器阶段经同一分区自动覆盖)
- **验收**:代理单测——公网 CONNECT 隧道建立、私网/元数据目标 403 拒绝、绝对形式 HTTP 转发、端口 0 随机分配。
### P1-5 SearXNG/LLM 配置 URL 深校验(域名 DNS 解析)
- `ssrf-guard.ts` 新增 `assertSafeConfigTargetDeep(url)`:域名做真实 DNS 解析(all:true),任一解析结果命中云元数据/链路本地(169.254/16)、0/8、组播保留段(≥224)、IPv6 等价段 → 拒绝;回环/RFC1918 放行(本地实例合法);DNS 解析失败放行并 WARN(配置期校验为纵深,运行时工具仍有独立校验)。
- `ipc/shared.ts` `assertSafeConfigUrls` 改为 async 并对 URL 类键(llm.baseURL / llm.fallbackBaseURL / searxng.url / app.updateFeedUrl)启用深校验;config:set / setBatch 两处调用点同步。
- **验收**`metadata.google.internal`、解析到 169.254.169.254 的域名(mock dns)被拒;localhost / 192.168.x / DNS 失败放行。
---
## P2 — 能力演进
### P2-1 会话回收站(30 天)
- DB 迁移(SCHEMA_VERSION 2→3):`sessions``deleted_at INTEGER NULL` + 索引;`sessions:delete` 改软删除(置 deleted_at);`list/getSession/getMessages/searchMessages` 全部排除已删除;新增 IPC `sessions:listTrash` / `sessions:restore` / `sessions:purge`(硬删除,级联消息);启动时与每 24h 定时清理超 30 天回收站;`data:clearSessions` 连回收站一并硬删。
- UI:Sidebar 底部「回收站」折叠区(列出已删会话 + 恢复/彻底删除,带确认)。
- **验收**:删除→列表消失、回收站出现→恢复回列表→彻底删除级联清消息;30 天清理函数单测;迁移测试升级(user_version=3)。
### P2-2 会话回放(JSONL 播放器)
- IPC`sessions:listRecordings(sessionId)`(扫描 workspace/logs/session_{id}_*.jsonl,带 mtime/size);`sessions:readRecording(relativeName)`(严格校验文件名模式与目录边界,返回解析后事件数组,单文件 ≤20MB 截断保护)。
- UI:Trace 面板新增「会话回放」入口 → 新组件 `SessionReplayPlayer`:文件选择、时间轴(iteration_start 定锚点)、播放/暂停/步进/速度、事件流渲染(思考/工具调用/结果/正文),复用 formattersEsc 关闭。
- **验收**:组件测试覆盖加载/步进/边界(空文件/损坏行跳过);IPC 路径逃逸拒绝测试。
### P2-3 electron-updater 自动更新接入
- 依赖 `electron-updater@^6``electron-builder.yml``publish: { provider: generic, url }`(默认取 app.updateFeedUrl 语义)。
- `update.service.ts` 扩展:保留手动 feed 比对 `check()`;新增 `autoUpdater` 薄封装(生产环境启动 5s 后 `checkForUpdates`available → 广播 `update:status` 事件 + toast;新增 IPC `app:updateDownloadAndInstall` 触发 downloadAndInstall,进度/完成/失败事件广播);dev 模式跳过;未配置 feed 时静默禁用。
- UI:设置 → 日志与数据 新增"检查更新/下载并安装"入口与进度显示。
- **验收**service 单测(stub autoUpdater 事件映射);IPC 面注册断言;dev 跳过断言。
### P2-4 输入框 @ 文件提及
- IPC`workspace:listFiles(query, limit)`(工作空间递归文件名索引,跳过 node_modules/.git/logs/.metona,≤2000 条);`workspace:readFileClip(relPath, maxBytes=512KB)`(路径边界 + 大小上限 + 文本判定,二进制拒绝)。
- UIChatInput 输入 `@` 触发 MUI Popover 文件联想(Fuse 模糊匹配,键盘上下/回车选中,Esc 关闭);选中插入 `@相对路径` token;发送前解析所有 `@path` → readFileClip → 以 attachmentstype=text, name=path)注入(复用既有附件管线与 LLM 提示契约);文件缺失 toast 警告并原样发送。
- **验收**:组件测试(联想弹出/选择插入/发送注入);IPC 边界测试(逃逸路径/超大/二进制拒绝)。
### P2-5 MCP Resources/Prompts 发现
- `mcp-manager.service.ts`:连接成功后 try/catch `listResources()` / `listPrompts()`(可选能力,失败置空不阻断);状态结构增 resources/prompts;新增 IPC `mcp:listServerContents(name)`
- UIMCPSettings 每个 server 增可展开「资源/提示词」区。
- **验收**mock SDK 的发现/降级测试;UI 渲染测试(有空/无空两态)。
### P2-6 文档对齐
- `docs/MetonaAI-Desktop 内部API请求与响应标准.html`Adapter 清单补 MiMo/OpenAI/Anthropic(移除"未实现"注记);流事件全集补 `stream_reset` 与 DONE.finishReasonFinishReason 章节补 MiMo `repetition_truncation` 映射说明。
- `README.md`:版本徽章 + v0.8.0 亮点表 + 配置说明对齐。
- **验收**:文档 diff 逐条核对实际实现。
---
## P3 — 测试与流程基建
1. **弱断言根治**registry.test.ts WEBP 恒真分支改单向断言;hooks-contracts.test.ts:251 自比恒真改真实断言;memory-manager.test.ts 空 token 断言补 `length===0`;其余环境依赖型断言复核确认。
2. **IPC 事件管道真实转发测试**agent.test.ts 新增回放缓冲 describe(真实 EventEmitter → streamEvent/stateChange 转发 → 按序缓冲断言)。
3. **useAgentStream hook 级测试**:done 收尾、截断提示、错误文案、reasoning/text delta 累积(jsdom)。
4. **sendStream thinking 参数断言**thinking 契约矩阵经 toNativeRequest 共享入口覆盖 send/sendStream 两路径。
5. **CI**electron-test 已是阻塞门禁(既有配置),新增用例纳入覆盖。
- **验收**:全部新增/修改测试在 `npm run test:electron` 下绿。
---
## 收尾
- `package.json` version → 0.8.0README 徽章同步。
- 全量 `npm run typecheck` + `npm run lint` + `npm run test:electron` 三绿。
---
## 验证记录(实施完成后逐项回填)
> 全局验证(2026-09-05):`npm run typecheck` 0 错误;`npm run lint` 0 问题;
> `npm test`(系统 Node2144 通过 / 301 按 ABI 设计跳过;
> `npm run test:electron`Electron ABI 全量)**2445/2445 用例全部通过,0 跳过**。
> (修订提交后复跑:thinking 相关测试矩阵全绿,见 P0-3 修订行。)
| 项 | 验证方式 | 结果 |
|---|---|---|
| P0-1 finish_reason 贯通 | engine-stream-contract.testDONE 携带 finishReasonstop/length 两用例);step.finishReason 记录;ipc/agent.ts 主管道与 SubAgent 管道 `finishReason ?? 'stop'` 真值接线(代码审查 + agent.test 管线测试) | ✅ |
| P0-2 空响应守卫 + 降级重试 | engine-stream-contract.test 6 用例:零产出→重试耗尽 ERRORreasoning-only+length→一次性降级重试(第二次请求 thinkingEnabled=false 断言)→COMPLETED;降级后仍 length→OUTPUT_LENGTH_EXCEEDED;有正文+length→COMPLETED 不重试;reasoning 自然结束→重试路径 | ✅ |
| P0-2 附带根治 | **abort 优先级缺陷**(新测试发现):abort 恰逢零工具调用轮被 COMPLETED 抢占吞成 completed → 前置检查修复;orchestrator 测试"被中断 SubAgent success=false"契约更新 | ✅ |
| P0-3 思考与预算对齐 | thinking-capability-gate.test 7 用例。**修订(用户意图优先)**:初版按 supportsThinking 元信息硬门控,经复核推翻——事故复盘中 vision-exp 元信息标注"不支持思考"、实际却产生了 8189 token 推理内容(元信息不可靠),且引擎已有完整兜底链(最大输出上限 → 空响应守卫 → 降级重试 → 结构化报错),硬拦截属替用户做决定。现 DeepSeek/MiMo/Agnes 三家完全遵循用户配置(元信息不符仅告警);**唯一保留门控是 Ollama** /api/show 探测——服务端硬协议约束(向不支持思考的模型发 think 每次请求 400),属协议正确性。测试反向钉住:vision-exp + 用户开启→照发 enabled+reasoning_effort;关闭/未配置→显式 disabledOllama 探测 false→不发 think / null→fail-open。小输出预算告警保留 | ✅(修订) |
| P0-4 渲染层提示 | use-agent-stream.test 5 用例:done 收尾;length 截断提示;output_length_exceeded 友好文案;reasoning/text delta 累积。i18n zh/en 全部出层 | ✅ |
| P0-5 四件套 | ①reasoning-only 终止判定(contract);②集成级空闲超时(engine-stream-reliability:挂死流→504fake timers);③SseUpstreamError(504)→引擎重试(同文件);④REASONING 中 abort→USER_INTERRUPT(同文件);⑤P4-2 30s 强制 abort 路径(同文件 fake timers | ✅ |
| FEAT-1 最大输出上限 | LLMSettings 新增「最大输出上限」项:`supportsOutputLimitConfig`model-capabilities.ts Provider 支持矩阵门控,六家均支持、数据驱动可扩展);展示模型 maxOutputTokens 上限与超限警告;保存经 llm.maxTokens 热生效(applyEngineConfigKey);typecheck + 表测沿用 | ✅ |
| P1-1a 后台会话回放 | ipc/agent.ts 有界缓冲(2000 条/4MB/LRU 50 会话)+ agent:getReplayState + 渲染层 stream-event-bus + setCurrentSession 回放恢复;agent.test 2 用例(按序缓冲/runId 记录/INIT 清空/无效参数) | ✅ |
| P1-1b abort 自愈 | useAgentStream stateChange THINKING 自愈(runId 匹配恢复 isStreaming);agent-store.abort IPC 失败回滚 + toast;注释与实现对齐 | ✅ |
| P1-1c 收尾兜底 | agent-store.sendMessageresolve 且 success=false→本地 error 收尾;resolve 后 2s 仍流式→finalize_missing 提示 + 收尾(currentSessionId 守卫) | ✅ |
| P1-1d 卡片清扫 | useAgentStream done/error 均调用 sweepInterruptedToolCallsmessages + traceSteps 的 executing/pending→error"已中断" | ✅ |
| P1-2 工具 abort 全覆盖 | http_requestssrfPinnedFetch 第 4 参);web_fetch(逐跳 signal + abort 感知 sleep + 浏览器阶段 webContents.stop);web_search(全链路 signal + 300s×0.8 时间预算 + 移除伪造 context 与死代码);code_searchrg 子进程 signal + AbortError 分支);gitrunGit signal 8 处调用点);delegate_task→orchestratorsignal 联动 abortSubEngine + finally 解除监听);ollama generate/embed AbortSignal.any 合并 | ✅(实现 + typecheck;子进程级 kill 依赖 Node execFile/spawn signal 原生语义) |
| P1-3.1 SSE 空 error 对象 | extractUpstreamErrorFrame 重构(显式 message/status/code 三标识判定);sse-stream.test 契约更新(空对象跳过 + 有 code 占位文案) | ✅ |
| P1-3.2 Ollama 外部信号 | generate/embed 增 signal 参数 + AbortSignal.any 合并 | ✅ |
| P1-3.3 UTF-16 | isBinaryFile BOM 前缀放行;filesystem-tools.test 契约更新(LE/BE 正常读取,utf-16le 解码断言) | ✅ |
| P1-3.4 tmp 碰撞 | filesystem.ts / file-editor.ts tmp 名追加 nanoid(6) | ✅ |
| P1-3.5 code_search 对称 | SearchFilesTool 新增 case_sensitive / context_before / context_after(独立前后文切片);回退路径原样透传 | ✅ |
| P1-3.6 node_modules | list_directory 新增 include_node_modules(默认 false 兼容) | ✅ |
| P1-3.7 崩溃自愈退避 | WindowManager 60s 滑窗 ≥3 次停止 reload + showErrorBox5 分钟稳定复位 | ✅ |
| P1-3.8 i18n 收口 | MemoryViewer→t('memory.empty')Sidebar riskLabels 补 critical | ✅ |
| P1-4 Pinned 代理 | pinned-proxy.tsCONNECT 隧道 + 绝对形式 HTTP + 可注入 resolver);BrowserWindowManager 分区接入(proxyActive 时让位用户代理);network-proxy 不覆盖已激活的 pinned 分区;pinned-proxy.test 9 用例(200 隧道透传/403 fail-closed/400 非法目标/幂等启动/深校验 5 例) | ✅ |
| P1-5 配置 URL 深校验 | assertSafeConfigTargetDeep__dnsLookup 可替换点 + DeepCheckSoftFailure 软失败);shared.ts assertSafeConfigUrls 转 asyncconfig:set/setBatch await 接入;测试覆盖元数据 IP 拒绝/公网放行/DNS 失败软放行/回环放行 | ✅ |
| P2-1 回收站 | DB SCHEMA_VERSION 3 + 迁移 10(存在性守卫,createTables 静态索引移除以兼容遗留库);session.service 软删除/listDeleted/restore/purge/purgeExpiredTrashsearchMessages 聚合剔除;IPC listTrash/restore/purgemain.ts 启动+24h 清理;Sidebar TrashSessionsPanel(恢复/彻底删除确认);session-trash.test 5 用例(Electron ABI 通过)+ 迁移矩阵契约更新 | ✅ |
| P2-2 会话回放 | IPC listRecordings/readRecording(文件名白名单 + 目录边界 + 20MB 上限 + 坏行跳过);SessionReplayPlayer(选择/时间轴/播放暂停步进/0.5-4×/事件渲染);DetailPanel Trace 入口;i18n zh/en | ✅ |
| P2-3 electron-updater | update.service 双轨(手动 feed 比对保留 + startAutoUpdater/getAutoUpdaterHandle);main.ts 生产环境启动 5s 静默检查 + update:status 广播 + 系统通知;IPC app:updateInstallpreload onUpdateStatus/updateInstallLogsSettings UpdatePanel(检查/下载安装/进度);electron-builder.yml publish generic | ✅ |
| P2-4 @ 文件提及 | workspace.listFiles2000 上限/噪声目录跳过/深度 8+ readFileClip(边界/512KB/NUL 拒绝/MEMORY.md 保护);IPC + preload + 类型;ChatInput@ 触发联想(Fuse 模糊 + 键盘导航)、applyMention 片段替换、发送时 extractMentions(邮箱防护/尾点剥离/≤5 个)→附件管线注入 + 失败 toast | ✅ |
| P2-5 MCP 发现 | connectServer try/catch listResources/listPrompts(可选能力降级空数组);getServerContentsIPC mcp:listServerContentsMCPSettings 每 server 展开"资源与提示词"i18n zh/en | ✅ |
| P2-6 文档对齐 | 内部 API 标准 HTMLAdapter 清单补 MiMo/更新 Anthropic/OpenAI 注记、MVP 表改为六家已实现、事件枚举移除 THINKING_* 补 STREAM_RESET、DONE.finishReason 语义与 repetition_truncation 映射说明;README:版本徽章 0.8.0 + v0.8.0 亮点表(P0-3 行随修订更新) | ✅ |
| P3-1 弱断言根治 | registry WEBP(确定性 WEBP 魔数单向断言);hooks-contracts 自比→真实断言;memory 空 token 补 length===0(另 2 处 GBK/mcp-if-else 属环境依赖型,复核确认保留原状) | ✅ |
| P3-2 IPC 管道 | agent.test 新增回放 describe:真实 EventEmitter → streamEvent/stateChange 转发 → 缓冲断言;既有 9 步编排/ERROR+DONE 广播断言沿用 | ✅ |
| P3-3 hook 级测试 | use-agent-stream.test 5 用例(done 收尾/截断提示/错误文案/思考与正文累积),jsdom 环境 | ✅ |
| P3-4 sendStream thinking | P0-3 修订契约覆盖请求体构建(toNativeRequest 为 send/sendStream 共享单一入口,门控矩阵即两路径契约) | ✅(共享入口口径) |
| P3-5 CI | CI electron-test 已是阻塞门禁(既有配置),本次新增全部用例纳入其覆盖 | ✅ |
| 收尾 | package.json 0.8.0typecheck/lint/双模式全量三绿(见顶部全局验证) | ✅ |