Releases
v1.4.5
Compare
Sorry, something went wrong.
No results found
[1.4.5] - 2026-08-07
Added
渠道设置支持更改已存在渠道的渠道类型:编辑表单中「渠道类型」由只读徽章改为可选下拉框(Gemini / OpenAI Compatible / OpenAI Responses / Anthropic 互转),切换前弹三语确认框提示(明示类型特有参数将重置);后端 updateConfig 放开 type 字段——类型变更时以新类型默认配置为基底重建,仅保留跨类型通用字段(名称、API Key、超时、重试、自定义标头/Body、上下文管理、Token 计数等),类型特有字段(API URL、模型列表、当前模型、高级选项、useAuthorizationHeader 等)重置为新类型默认值,避免旧类型字段残留(如 Gemini thinkingConfig 残留到 Anthropic 配置)污染请求;createConfig/updateConfig 新增运行时渠道类型校验(非法 type 直接拒绝,不再落入 default 分支产生无 url 的死配置);显式传入的更新字段优先级最高;设置导入覆盖路径同步获得类型变更能力;若变更的是当前激活渠道,前端同时清空会话级模型覆盖(selectedModelId),避免残留旧类型模型 ID 被当作显式模型发送。新增 ConfigManager.updateConfig.test.ts 回归测试(11 用例:类型可改且 ID/createdAt 不变、类型特有字段重置、通用字段保留、显式覆盖、互转默认值、普通更新不回归、同类型不触发重建、配置不存在报错、非法 type 拒绝(更新/创建)、openai 特有字段重置)。渠道类型运行时校验常量 CHANNEL_TYPES 与 ChannelType 类型定义同源(configs/base.ts),新增渠道类型时类型与守卫同步维护。
GitHub Releases 自动更新:扩展启动后延迟 10s 检查 Komeiji-Shiki/Gray-Code 最新 Release(距上次检查不足 24 小时跳过,时间戳存 globalState;设置页「立即检查」忽略节流),有新版本时前端弹窗展示 Release 说明,用户确认后自动下载 vsix 并调用 workbench.extensions.installExtension 安装、提示 reload 窗口生效;设置页「通用」新增「自动更新」区块(启用开关 + 立即检查 + 一键更新 按钮——一键更新自动完成「检查 → 下载 vsix → 安装」,安装后提示重启窗口即可生效);请求复用渠道代理配置(createProxyFetch),版本检查超时 10s / 下载 120s,失败静默不打扰;新增 checkForUpdates 设置项(默认 true,可关闭,随 Settings Sync);三语 i18n + 设置搜索索引;新增 webview 消息 getUpdateStatus / checkUpdateNow / installUpdate / updateNow / openUpdatePage 与 UpdateHandlers(updateNow 为检查安装一条龙);后端新增 UpdateChecker 模块(版本比较 / 节流判断 / API 响应解析为纯函数,update 状态机覆盖 disabled / idle / checking / upToDate / updateAvailable / error)及 33 个单元测试(含下载安装 mock、handler 安装/重启流程)。
GitHub Actions 发布流程 .github/workflows/release.yml:推送 v* tag 时自动 npm ci + 构建 + vsce package 生成 graycode-<tag>.vsix 并发布到 GitHub Releases(自动生成 release notes),替代手动打包上传,为自动更新提供稳定 vsix 资产。
提示词预设(prompt entries)的 assistant 条目新增「伪造思考过程」(fakeThought):可额外配置一段伪造的 AI 思考内容,组装请求时以 thought part(thought: true)附加在临时 assistant 消息正文之前,模拟一轮带推理过程的回复;是否随请求回传由渠道「发送历史思考内容」(sendHistoryThoughts)开关决定——显式关闭时不回传(剥离 thought part、正文照发),与真实历史思考语义一致;过滤在发送侧执行(applyPromptContextThoughtPolicy),不写入 turnDynamicContext 缓存(同一回合缓存可能被不同渠道复用)。
渠道设置「当前轮次回传配置」与「历史回合回传配置」合并为单一「思考回传配置」区块(内部按当前轮次/历史回合分组,控件与默认值不变),三语 i18n 同步(common.thinkingBackfill,渠道级覆盖键迁移为 thinkingBackfill.*)。
Changed
排队消息提前投递(P1):排队消息不再等待整个 LLM 回合完整结束才发出——前端在「非终结 toolIteration」边界(后端已把当前动作批次的工具结果 settleFunctionResponses/addContent 全部落盘,动作彻底结束)自动取出下一条排队消息,按与「立即发送」相同的方式替换当前回合(cancelStream({preserveSubAgents: true}) + 新 chatStream);后端 H1 写序保证(webview 层 awaitOldStreamCompletion + 后端 waitForOldStreamExit)确保旧流完全退出后才写入新用户消息,插入点之前的完整历史保持原样、不丢序;每执行完一个动作投递一条,剩余排队消息在回合结束时照常投递;发送失败自动放回队首,不静默丢失;跨会话消息跳过逻辑与 processQueue 一致;终结性 toolIteration(审批门闸/工具取消)与 awaitingConfirmation 阶段不触发,保持原等待语义。新增 queuedMessageEarlyEmit.test.ts(12 用例:streamHandler hook 接线 5 + 投递行为 7,含重入保护、跨会话跳过、失败回队首、投递窗口会话切换与并发发送者防护)。processQueue 与 processQueueAfterAction 的「取当前会话下一条消息」逻辑提取为公共函数 takeNextForConversation(跨会话跳过语义单一实现)。
Fixed
修复编辑消息、删除消息或重试截断历史后误报 VALIDATION_ERROR: Invalid tool history: orphan_function_call:共享 transcript 删除逻辑现在会识别“functionResponse 被本次删除、对应 functionCall 仍保留”的配对断裂,并在同一次仓储变更中把调用标记为 rejected(不再伪造“用户拒绝执行此工具”响应);deleteMessagesInRange、deleteToMessage/truncateFrom 与单条逻辑删除统一继承。settleFunctionResponses 同步加固:迟到真实结果仅写回当前历史仍存在的调用,落盘后清除 rejected;调用所属旧分支已被截断时丢弃迟到响应,避免反向制造 orphan_function_response。新增 orphanFunctionCallRepair.test.ts 覆盖编辑分支复现、截断/区间/单条删除、并行调用与结算时序。
修复后台任务回执([Background task completed])加载旧对话时显示为普通用户消息:StreamRequestHandler.handleChatStream 解构前端 chatStream 请求时漏透传 source 字段,后端落盘时 request.source 为 undefined,回执消息的 source='background_task' 从未写入历史——当前会话靠前端内存正常显示,重新加载对话后渲染成完整正文的用户消息(占地超大且无折叠卡片);现补上 source 透传(新数据落盘即带标记),并在 getMessagesPaged / getMessages 返回路径对历史遗留的「缺 source 且以 [Background task completed] 前缀开头的用户消息」做展示层归一化补 source='background_task'(只补内存不写盘,避免重写历史文件);新增 ensureBackgroundTaskSourceForDisplay 单元测试(缺 source 补标记 / 已有 source 不覆盖 / 普通消息与 functionResponse 不误判 / 无 parts 安全跳过)。
渠道设置「启用此配置」开关移至表单顶部:此前该开关位于配置表单最底部,用户进入设置时下意识以为没有启用功能;现置于 API URL 之前(配置选择器/新建按钮正下方),一眼可见,data-search-anchor="channel-enabled" 搜索锚点保留。
修复 memory_config 只读调用在工作区记忆目录尚未初始化时误报「workspace URI could not be resolved」:此前纯读走 createIfMissing=false,工作区目录不存在时返回 null,被笼统报成 URI 解析失败;现区分「URI 不可解析」与「工作区记忆目录未初始化」两种失败——前者才报解析错误,后者纯读时回退显示全局配置并标注 workspaceNotInitialized(保持无磁盘副作用,不创建目录),带更新参数时仍正常创建目录并写工作区独立配置;memory_zoom 只读调用同样区分两种失败,工作区未初始化时改报明确的 Workspace memory is not initialized for this workspace;新增 memoryConfigTool.test.ts 回归测试(6 用例:无工作区读全局、工作区独立配置隔离、未初始化纯读回退标注且无副作用、未初始化带更新参数建目录、URI 不可解析报错、全局未初始化报错)。
修复 VSCode 窗口聚焦时仍播放任务完成/错误提示音:音效控制器 handleSoundEvent 此前只在文档隐藏(窗口最小化/切走)时聚合不播,窗口聚焦时无论焦点在哪都直接播放;现由扩展侧监听 vscode.window.onDidChangeWindowState 把窗口焦点状态(windowFocusChanged 命令)推送到前端,音效按窗口焦点分级——VSCode 窗口聚焦(用户看得见界面、事件结果已可见)不播,窗口失焦(切到其他应用)时照常播放提醒;文档隐藏期间聚合的事件在恢复可见时仍补播一次。新增 soundEventController.test.ts(7 用例:默认聚焦不播、聚焦不播、失焦播放、失焦后聚焦不播、隐藏聚合后恢复补播、visibilitychange 集成、事件过期丢弃)。
修复流式输出自动滚动吸底的拉回与丢吸底问题:① 正文主滚动容器(CustomScrollbar sticky-bottom)此前 wasAtBottom 在 scroll 事件的 rAF 回调里才更新,滞后一帧——流式期间 MutationObserver 驱动的贴底写入读到陈旧状态,把刚向上滚动的用户拉回底部(「使劲滚才能滚上去」);现 scroll 事件同步 更新吸底状态,用户滚动意图立即生效。② 贴底写入后立即用实时 scrollHeight 复验,大段输出/md 异步解析会让 scrollHeight 在写入后继续增长,误判「用户滚离」→ 永久丢吸底;现程序贴底写入记录目标 scrollTop(programmaticScrollTop),其触发的 scroll 事件按写入值比对识别为程序行为、保持吸底状态,吸底状态只由用户滚动改变。③ 思维链中展开(MessageRenderBlock)shouldStickBottom 按 scrollHeight - scrollTop - clientHeight 复验,用户滚回底部后内容大段增长(scrollTop 未变、scrollHeight 变大)同样误判丢吸底;现改用 scrollTop 变化检测——scrollTop 未变 = 用户没动,内容增长不改变吸底意图。④ promote 后的 nextTick 贴底校正增加当前位置复验,用户刚滚离(scroll 事件滞后)不再被拉回。新增 CustomScrollbarSticky.test.ts(3 用例:底部增长贴底、滚离不打扰、写入后增长不丢吸底)与 MessageRenderBlock 中展开增长不丢吸底回归测试。
修复高 tps/高刷新率下流式输出「使劲滚才能滚上去」与代码块渲染后丢吸底:① 高 tps 下内容增长会抵消滚动距离(滚 100px 内容长 80px),且 scroll 事件异步派发滞后于 rAF 贴底写入——用户滚动后同一帧的贴底写入先把 scrollTop 拉回、随后派发的 scroll 事件又被 programmaticScrollTop 比对误判为程序行为,用户每帧都被拉回底部;现监听 wheel/触摸板输入事件(输入处理阶段同步派发、早于 scroll 事件与 rAF,程序写入 scrollTop 不会触发)标记 250ms 冷静期,期间不执行贴底跟随,让用户滚动真正生效;滚动条拖动(handleThumbMouseDown)同样标记冷静期;中展开(MessageRenderBlock)同步加 @wheel 冷静期短路,重新进入中展开时重置。② 代码块 fence 闭合后遇空行触发渐进 markdown promote,程序贴底写入(CharFlow scrollToEnd / promote 后 nextTick 校正)改变了 scrollTop 但未同步记录,紧随其后的代码块异步渲染(hljs 高亮)让 scrollHeight 骤增——中展开的 scrollTop 变化检测把程序写入误判为「用户滚动」→ 位置复验按涨量判定「不在底部」→ 丢吸底;现 shouldStickBottom 返回贴底前用微任务读回写入后的实际 scrollTop(浏览器对 scrollTop 赋值有钳制,读回最准),promote 校正写入后同步记录,程序写入不再触发变化检测误判。新增测试:CustomScrollbarSticky 冷静期 2 用例(wheel 后不拉回、冷静期过后恢复贴底)、MessageRenderBlock wheel 抵消滚动距离 1 用例 + 贴底写入后代码块异步渲染高度骤增不丢吸底 1 用例。
修复设置页「保存提示词」误报保存失败(实际已保存):webview 消息处理走串行队列(messageHandlingQueue),countSystemPromptTokens(token 计数调用渠道 API)、tokenizer.getResource(首次下载词表,分钟级)、exportPromptModes/settings.export/settings.import/storagePath.selectFolder(showSaveDialog/showOpenDialog 模态对话框,打开期间 handler 一直 await)、storagePath.getStats(大目录统计)等慢 handler 会长期占住队列——期间用户点保存,savePromptMode 排队超过前端 180s 超时被误报「保存失败」,而队列轮到时后端实际保存成功;现将上述类型移入 NON_BLOCKING_MESSAGE_TYPES(fire-and-forget,handler 内部 sendResponse 仍正常路由回发起方),并同步加入前端 UNBOUNDED_REQUEST_TYPES 超时豁免(对话框/下载/导入本身不再超时误报);前端 saveConfig 保存成功后的 token 计数改为不阻塞保存成功反馈(void countTokens())。新增 messageRouterNonBlockingBehavior.test.ts(慢 handler 不阻塞后续消息 + 普通 handler 阻塞对照组)与 promptSaveChain.test.ts(Vue reactive Proxy 数据保存链路回归:序列化、postMessage、响应路由全链路)。
修复设置保存「每次报失败但数据实际已写入」的根因:VSCodeSettingsStorage 的 SYNCABLE_KEYS 含 checkForUpdates,但 package.json 的 contributes.configuration 未声明 graycode.checkForUpdates——VS Code 对未注册配置键调用 config.update 直接 reject;保存时 Promise.all 并发写全部变更键,已注册键(如 toolsConfig)先成功落盘、未注册键(checkForUpdates,默认值 true 且从未写入过、快照恒为空)抛错,整体 save 失败——前端报「保存失败」但提示词已写入 settings.json(重启后仍在),且每次保存必然触发。现补上 graycode.checkForUpdates 声明(type: boolean, default: true);新增 vscodeSettingsStorageRegisteredKeys.test.ts 防回归——断言 ALL_CONFIG_KEYS 每个键都已注册(ALL_CONFIG_KEYS 改为导出)。
修复编辑首条(根)用户消息后会话「无论如何都不发消息」:编辑根节点时前端自动降级 keep(原地保存),但 editAndRetry 仍进入流式等待状态(设置 isStreaming/isWaitingForResponse)且依赖后端 complete 事件复位——keep 模式后端不重新生成,一旦 complete 丢失(视图重建/IPC 抖动/后端异常)状态永久残留,sendMessage 会把后续所有新消息拦截成 deliverInterruptMessage(投递到无人消费的 inbox),表现为停止/重试/重新保存全部失效。现 keep 模式不进入流式等待状态 (不设 isStreaming/isWaitingForResponse/streamingMessageId,activeStreamId 保留用于 error chunk 路由),replayBranchStreamAfterError 的 keep 重放同步处理;后端 keep 分支 complete 改为不带 content (此前携带主历史最后一条消息,可能是 user 角色——前端残留占位时会把 user 内容替换进 assistant 消息,ChatStreamCompleteData.content 改为可选,与前端 resetTerminalStreamState 的「无 content 终结 chunk」协议一致);编辑对话框对首条消息显示提示并禁用「保存」(branch)按钮(仅保留「原地保存」),消除「保存后应重新生成」的误解。新增/更新测试:keep 模式不进入流式等待状态断言、complete 无 content 路径回归。
修复代码块渲染后无法复制(复制按钮被净化移除):sanitizeHtml 的危险元素黑名单包含 button,会把 Markdown fence 渲染器生成的代码块工具栏复制/换行按钮一并移除——默认渲染路径(AI 消息正文)下代码块只有标题栏没有复制按钮,用户无法直接复制代码(测试此前只在 artifactSafe 路径断言按钮存在,default 路径被净化后一直缺失)。现 sanitize 对 class 受控的工具栏按钮(code-tool-btn,仅 fence 渲染器产出)放行,其余 button 照旧移除,on*/style 等危险属性剥离逻辑不受影响;复制执行同时加固——navigator.clipboard 不可用(VSCode Webview 非 secure context)或写入被拒时回退 textarea + document.execCommand('copy'),失败给按钮短暂红色反馈(不再静默 console.error)。新增测试:default 路径复制按钮保留 + data-code 可解码、copyToClipboard 兜底链(clipboard 缺失/抛错/双失败/选区恢复,5 用例)。
修复首条(根)用户消息无法「保存并重新发送」:此前根节点无父节点可挂编辑候选(BranchGraph 单根模型),编辑对话框「保存」按钮被禁用、store 层 branch 自动降级为 keep(只改文本不重新生成)、后端 resolveEditTargetNode 直接拒绝根节点 branch 模式——用户编辑首条消息后无法让 AI 按新问题重新作答。现整条链路支持根节点编辑重生成(TREE-03-R):后端 branch 模式对根节点放行,执行「原地改写根节点文本 + 截断其后主历史(旧消息已归档进分支图 sidecar)+ 新建模型候选重新生成」,旧回答保留为可切换候选(与 reroll 同语义),isFirstMessage 动态判断(根节点编辑后刷新系统提示词);前端 editAndRetry / restoreAndEdit 不再对根节点降级 keep,branch 模式原样透传(截断窗口 + 流式占位 + 分支图刷新标记),「原地保存(keep)」仍由编辑对话框显式选择;编辑对话框「保存」按钮对首条消息恢复可用(title 提示保存将重新生成后续回复),三语 i18n 更新。新增/更新测试:resolveEditTargetNode 根节点 branch 放行(图/线性模式)、根节点编辑重生成编排(改写 + 截断 + 候选 + finishReroll 回填 + 旧回答切回)、前端根节点 branch 流式流程。
修复「伪造思考过程」(fakeThought)内容重启后丢失:前端 PromptSettings.vue 的 normalizePromptEntries 重建条目对象时只保留 id/name/type/enabled/role/content/order,fakeThought 字段在加载(loadModeConfig)与保存(saveConfig)两条路径都被丢弃——保存时发给后端的 promptEntries 已无 fakeThought,落盘为空,重启后输入框自然空白;现归一化时保留 fakeThought,isSamePromptEntries 同步纳入比较(修改伪造思考内容后「未保存切换模式」提示才准确)。新增 PromptSettings.test.ts 组件级回归测试(3 用例:加载回填、保存保留、修改后保存提交最新值)。
Performance
存档点 manifest 懒加载(CPF-LAZY-1):从 schema version 2 起,重量级 files 映射(全工作区哈希表,大工作区可达 10-20MB)从 manifest.json 拆出,独立存放于 checkpoints/cp_xxx/files.json——manifest.json 只保留轻量元数据(workspaceRoots/emptyDirs/changes/excluded/ignoreSnapshot)。读取路径分级:存档列表摘要、设置页排除清单详情、恢复排除说明等只消费元数据视图(loadManifest),不再为少量字段解析整张文件哈希表;完整文件映射仅在恢复链构建、增量比较、链合并等真正需要的路径按需懒加载(loadManifestWithFiles / enrichRecord),经 IPC 下发给前端的 checkpoint.getManifest 也同步改为轻量视图。旧格式(v1 内联 files)首次读取后 best-effort 自动拆分为新格式落盘,存量存档渐进迁移:轻量读取路径解析一次后 files 进缓存、零写放大 ,拆分落盘只在完整数据读取(恢复/增量比较/合并)时触发,一次 10-20MB 级写入后后续读取全部走轻量路径;拆分失败/未发生时的 v1 存档由内联兜底继续提供数据,不误判丢失;写入顺序保证 manifest.json 为提交点(先写 files.json 再写 manifest.json,中途崩溃不会出现「新布局指向缺失文件」的不一致)。双缓存隔离:轻量元数据缓存维持既有 LRU 上限(32 条),files 映射缓存按需加载并设独立 LRU 上限(8 条),避免长驻 32 份 10-20MB 大对象;clearCache/删除路径同步清理双缓存。完整性检查(integrityCheck)同步支持 v2 拆分布局(files.json 缺失/损坏/ID 不符/形状非法分别上报)。新增回归测试:拆分写入、懒加载不触碰 files.json、files.json 缺失时元数据可读而完整数据显式失败、v1 读取后拆分迁移、双 LRU 淘汰、完整性检查 v2 布局(10 用例)。
存档点 manifest 懒加载后续加固(CPF-LAZY-1 审查优化):files.json(10-20MB 级)改为紧凑序列化(无缩进)落盘,减小磁盘体积与序列化开销(manifest.json 保持缩进可读);manifest 版本校验要求整数(非整数如 1.5 一律视为损坏走迁移/回退,不再按未知布局缓存导致数据丢失误判);writeManifest 写盘失败时清空该存档双缓存,避免链合并等路径在写盘前修改过缓存对象后残留「内存与磁盘不一致」状态。新增回归测试:紧凑序列化无换行、version=1.5 迁移落盘、写失败清缓存(3 用例)。
存档点 manifest 懒加载安全加固(CPF-LAZY-1 交叉审查):files 映射形状校验拒绝数组(typeof [] === 'object' 曾会让损坏的 files.json 被当作「空工作区」进入恢复流程,工作区全部文件被误判为 untracked 可删除);manifest 元数据字段补形状校验(缺 excluded/ignoreSnapshot 等的损坏 v2 manifest 不再让列表摘要/恢复排除说明 TypeError);L5 守卫用 record.fileCount 区分「真空工作区存档」(合法空快照可恢复)与「数据丢失」(显式报错);同一存档的磁盘写入经 per-checkpointId 单飞队列串行化(消除并发迁移/合并共享 tmp 文件的 ENOENT 竞态与 files.json/manifest.json 配对错乱);writeManifest 校验 manifest.checkpointId 与参数一致;legacy 恢复扫描跳过 manifest.json/files.json/*.tmp 元数据文件;integrityCheck 与运行期对未知版本(>当前)同口径(报 warning 不深校验);clearCache('') 不再误清全部缓存;checkpoint.getManifest 返回浅拷贝防外部污染缓存。新增回归测试:files.json 数组拒绝、v1 内联数组拒绝、缺元数据字段迁移、真空存档可恢复、checkpointId 不一致抛错、clearCache('') 语义、并发写串行化、integrityCheck 未知版本(8 用例)。
[1.4.4] - 2026-08-07
Added
设置页新增设置项搜索:标题栏搜索框实时过滤(结果下拉 + 侧边栏命中页签高亮、未命中置灰),键盘上下选择/回车跳转,点击结果自动切换页签并滚动定位(节标题或精确锚点 + 1.6s 闪烁高亮);内置中/英/日三语关键词索引(SEARCH_INDEX),17 个设置组件 93 个设置块加 data-search-anchor 精确锚点,具体设置项全部可搜可直达;空结果提示,三语 i18n 同步。
记忆隔离(移植自下游桌面版):永久记忆分为全局记忆 (所有工作区共享)与工作区记忆 (每个工作区独立存储,位于 <dataPath>/memory-workspaces/<hash>/,按 sha256(工作区路径) 前 16 位分目录,含 scope.json 元信息);对话创建时绑定当前工作区(createConversation 既有 workspaceUri 参数生效),工具执行层经 ToolContext.activeWorkspaceUri 注入(ToolExecutionService 调用方传入或按会话元数据兜底解析,ToolIterationLoopService 每回合加载一次),记忆工具按工作区路由、无工作区一律回退全局记忆。
双作用域合并唤醒与检索:memory_wake 依次唤醒全局段 + 工作区段(--- Global memory --- / --- Workspace memory (<文件夹名>) --- 标注),两段独立分页、续读越界自动跳过、两段都读完才算 awake,压缩提示合并输出;memory_recall 同时搜索两个作用域并标注来源、命中数相加;memory_note / memory_compress / memory_forget / memory_config / memory_zoom 按当前对话工作区路由写入/操作,工作区 id 空间独立无需去重。
设置页记忆分区:记忆条目区新增「全局记忆 / 工作区记忆」作用域切换 tab(工作区 tab 下拉选择「当前打开的工作区 + 已有记忆数据的工作区」,未建记忆的工作区惰性创建);所有记忆增删改查/配置消息支持 workspaceUri 参数,新增 listMemoryScopes 消息枚举 scope;运行时参数(wakeLines 等)按作用域独立保存;三语 i18n 同步(scopeGlobal/scopeWorkspace 等 6 键)。
记忆作用域数据缓存与切换即时渲染:每个作用域最近一次成功加载的条目/配置缓存在前端,切换作用域瞬间直接渲染目标作用域缓存数据、后台静默刷新(无中间加载占位帧);配合既有请求序号竞态守卫(过期响应丢弃)、静默配置刷新(不触整页 loading)与三段互斥渲染(加载占位/空态/列表),彻底消除切换瞬间的内容闪烁与列表高度塌陷空白帧。
新增回归测试 memoryWakeScopes.test.ts:无工作区只输出全局段(旧行为不回归)、双作用域分段标注与互不混入、工作区无记忆只出全局段、双空输出 No memories yet、memory_recall 合并命中与来源标注(6 用例);前端新增 MemorySettings.test.ts 组件测试:切回已加载作用域条目立即渲染(缓存直出、无加载占位中间帧——防单帧闪烁回归)、未选工作区不发请求、seq 竞态守卫丢弃过期响应(4 用例)。
记忆条目批量删除:设置页条目列表新增多选复选框与全选工具条(每行可勾选,删除所选 N 条),三语确认框确认后经新消息 deleteMemoryEntries 一次批量删除——后端 MemoryManager.deleteEntries 对乱序/重复 id 去重排序、相邻 id 聚合为闭区间、从大到小逐个 deleteRange(删除大 id 不影响小 id 索引),删除后剩余记忆重编号、相关树摘要清空;单条删除按钮保留。
原始记忆条目管理补齐「手动添加」与「单条删除」(移植自下游桌面版):设置页记忆条目区新增手动新增输入框(等价 AI 的 memory_note,支持 Ctrl+Enter / Cmd+Enter 快捷提交,实时 UTF-8 字节计数对齐后端 entryChars 校验,成功提示返回分配的 ID 并自动刷新列表);条目行新增删除按钮(三语确认框,删除后其后条目 id 前移重编号、覆盖块的树摘要一并清空,下次 compress 重建);getMemoryEntries 支持 limit 截断(默认 5000,超限响应 truncated 标志并在列表顶部提示,海量记忆不再冻结设置页)。
记忆引擎健壮性增强(移植自下游桌面版):MemoryManager.deleteEntry 单条删除(读全量 → 过滤 → 重编号 → tmp+rename 原子写回,崩溃安全,不连坐 truncateLog 的截断语义);note/updateEntry 按整条固定宽度记录精确校验容量(含 #<id> <date> 头部开销,entryChars 配置上限同步收窄到 LOG_REC - 1 - 23,不再在 pad() 处抛晦涩的 Too long);records() 只解析完整记录(崩溃撕裂的尾部半条不再被当作垃圾条目,下次追加时 repair 修复);count/logScan 只吞 ENOENT(其余 IO 错误上抛,不再谎报「没有记忆」);wake 连续原始块合并为一次 logSlice 批量读取(消除逐块 open/read/close 的文件句柄风暴);新增 totalEntries(O(1) 计数)。
新增回归测试:addMemory.test.ts(note/updateEntry 记录容量校验、listEntries(limit) 截断语义、撕裂尾部容错、wake 批量读取一致性)、deleteEntry.test.ts(中间/尾部/唯一条删除、非法 id、树摘要清理与重编号连续性)、deleteRange.test.ts(闭区间删除/单点区间/首部整段/非法区间/树摘要清理/批量乱序去重/聚合删除/追加连续性,11 用例)、memoryForgetTool.test.ts(工具三模式:单 ID 删单条、闭区间删范围、块 ID 丢摘要不触原始记忆、未初始化与越界返回失败结果,5 用例)。
Changed
分支对话工作区绑定语义修正:createBranchConversation 不再用「当前激活工作区」兜底(此前会覆盖后端「继承源对话工作区」逻辑,把分支错误绑定到当前活动项目),未显式传 workspaceUri 时一律由后端继承源对话绑定;ConversationManager.setWorkspaceUri 签名放宽支持传 undefined 解绑对话(元数据持久化时字段自动移除)。
memory_forget 工具语义修正:单个数字 ID 不再「截断删除 ID ≥ 该值的全部记忆」,改为只删除这一条 原始记忆(其余保留、id 前移重编号,等价设置页单条删除);新增闭区间模式 ——逗号分隔 "1,3" 删除 ID 1 到 3 的所有原始记忆(含端点);破折号块 ID(如 "16-31")保持「仅丢弃树摘要」原语义;工具声明与前端展示(descriptionFormatter 区分 Delete memory / Delete memories / Forget block)同步;底层新增 MemoryManager.deleteRange(lo, hi)(闭区间原子删除)与 deleteEntries(ids)(批量聚合),deleteEntry 委托 deleteRange(id, id)。⚠️ 破坏性变更提示:旧语义下 memory_forget "N" 表示「截断删除 ID ≥ N 的全部记忆」,新语义只删一条;如需清空全部记忆请改用设置页全选删除,或显式闭区间 "0,<最大id>"。删除会触发后续 id 重编号,成功消息会提示重新 memory_wake 刷新后再继续删除。
记忆容量校验落位优化(审查 PR #18 后):note 的整条记录容量校验从锁外按 logLen 估算 id 改为在 logAppend 锁内按真实分配 id 执行(消除并发追加时 id 位数增长导致的估算偏差,彻底杜绝 pad() 处晦涩的 Too long);entryChars 配置上限的头部开销魔数 23 提炼为 MAX_HEADER_BYTES 常量(附推导注释);设置页条目列表加载上限 5000 抽为 ENTRIES_LIMIT 常量(与后端默认 limit 对齐),手动添加成功提示对齐 saveConfig 的 3 秒自动清除。
思维链(思考块)视图从两态升级为三段式(对齐后台任务回流消息):折叠 (只保留头部标题行)/ 中展开 (默认,固定约 10 行滚动查看)/ 完全展开 ;头部单击三档循环切换,头部右侧新增三个精确模式按钮(chevron-up / list-flat / chevron-down,@click.stop 防冒泡);ThoughtViewMode 类型与 getRenderBlockMemoDeps 签名同步更新(isThoughtExpanded → thoughtViewMode),清理 MessageItem 中遗留的 thought 样式死代码;三语 i18n 同步;MessageRenderBlock 测试重写为三态覆盖。
思维链中展开(medium)模式完善:中展开与完全展开同源 markdown 渲染 (流式时渐进 markdown 即时渲染已定型完整段落 + 未完成尾巴 CharFlow 托管,非流式 MarkdownRenderer 直接渲染,不再显示纯文本);可中断自动吸底 ——内容更新自动贴底跟随最新,用户向上滚动超过 40px 阈值即暂停跟随不打扰,滚回底部附近自动恢复(CharFlow 新增 scrollContainer / stickBottom 回调,贴底写在滚动容器上);裁剪提示 ——单段超长内容触发 tailWindow 裁剪时,内容区顶部显示「内容过长,仅显示最近部分,请使用完全展开查看」提示条(CharFlow 新增 onTrimmed 回调,仅流式期间显示);三语 i18n 同步。
思维链中展开吸底稳定性修复:① promote 剥离内容时 CharFlow host 同步变矮、MarkdownRenderer 下一 tick 才渲染变高(两段式高度变化),原同步校正停在中间——改为 nextTick 后按最终 scrollHeight 校正;② scroll 事件由浏览器合帧派发、滞后于实际滚动,用户刚滚离底部时 append 可能误拉回——shouldStickBottom 增加实时位置复验(距底 ≥40px 即同步置 false 不贴底);③ 新增 userScrolled 标志:未滚动/重新进入中展开时无条件贴底(避免初始 scrollTop=0 被复验误判),滚动后按位置复验;注册中展开时重置吸底状态;新增 3 个回归测试(滞后复验 / nextTick 最终高度校正 / 原有恢复路径)。
思维链自动视图模式:思考中默认「中展开」;思考与输出全部结束后自动折叠为单行第一行预览 (折叠视图新增内容预览区:非流式取思考内容首行,流式由 CharFlow 单行模式实时显示最新字符——squashLineBreaks 折叠换行 + tailWindow 有界 + followEnd 跟随);用户手动切换过视图模式后自动切换不再干预(尊重用户选择);已结束消息重建后初始即为折叠态;新增折叠预览与自动模式切换测试。
上下文总结(自动 + 手动)从「物理删除被总结消息」改为「逻辑截断」:被总结区间的原始消息打 isSummarized 标记完整保留在历史中(不再从磁盘消失,history_search 现在可以检索到被压缩的原文),发送给 AI 与 token 统计跳过被总结区间(ContextTrimService 统一过滤);首条用户消息永远发送 (任务锚点,prependFirstUserMessage 在所有发送路径前置,含手动总结边界与 trim 裁剪场景);isRealUserMessage 排除 isSummarized,回合识别/总结规划/isFirstMessage 判断(过滤后活跃消息数)全部适配;协议 insertIndex 改为总结消息插入位置(= summarizeEndIndex)、removedCount 改为本次标记的消息数,前端 handleAutoSummary 同步为「标记本地消息 + 插入总结」(不再删除/平移索引);消息列表在最后一个总结消息后渲染横线分隔「已总结 / 未总结」区域(原文照常显示不折叠);自动总结的 STALE_RANGE 并发校验与低质量总结拒绝保留;历史文件无限增长为接受项(原文永不清理)。
新增「恢复原文」能力(逻辑截断反向操作):总结消息卡片新增恢复按钮(restoreSummarizedMessages API),点击后取消该总结覆盖区间的 isSummarized 标记并删除总结消息本身,发送起点回退到上一个总结(或 0),原文重新参与发送与统计;删除总结消息(deleteMessage / deleteMessagesInRange 命中总结消息)同样自动恢复覆盖区间,杜绝「既无总结文本也无原文」的上下文真空(restoreSummarizedRange 纯函数,覆盖区间 = 上一个总结之后到该总结,从晚到早逐个恢复多总结场景);三语 i18n 与新增回归测试(summarizeRestore.test.ts 7 用例)。
全链路性能与资源占用优化(移植自下游桌面版,仅含前后端可移植部分):
后端读路径缓存:checkpoint 节点反查(getMessageNodeIdAt)300ms 短 TTL + LRU 缓存,一轮对话免十几次全量读盘;分支图读路径只读引用缓存(60s TTL + 200 会话 LRU + mtime/size 外部改写校验),大图不再每次迭代全量 structuredClone;模型列表 5 分钟 TTL;工具声明指纹缓存(tools JSON Schema 构建结果按声明/模式/工具逻辑/MCP 版本缓存);会话元数据读路径与分支图读路径解耦。
热路径算法:上下文裁剪起点由 O(候选历史) 改为前缀扫描 O(n) 预计算;运行时上下文回合内复用,同一回合不再重复生成;流式响应 chunk 用 offset 游标解析,消除每个数据包 Buffer.concat / 字符串拼接的 O(n²)。
前端流式渲染:hljs 已知语言高亮缓存(流式期间同一语言块不再反复全量高亮);usedTokens 增量指纹(每 chunk 免全量历史扫描);todo 快照尾部增量重放(每 chunk 免全量重放,前缀引用与响应表校验不一致时自动回退);MessageList 构建/todo sticky 列表短路(无 build / 无 todo 时免扫描)。
前端高频路径:i18n t() 翻译缓存(无参键免 split + 查找);CustomScrollbar 值相等检查(无变化不写响应式 ref)+ rAF 节流 + 贴底阈值;平滑流式增量基线(每 chunk 免累计文本 slice)。
webview:分支图富化响应前浅拷贝(配合只读引用缓存契约);requiresJsonRoundTrip 小 payload 短路(高频小消息免分配 visited Set 深遍历);广播直接迭代订阅者 Set(免每 50ms 复制订阅者集合);getExtensionVersion memo。
Fixed
修复自定义存储路径迁移遗漏记忆目录:STORAGE_SUBDIRS 此前不含 memory(全局记忆)与 memory-workspaces(工作区记忆,含各 <hash>/ 子目录),更换自定义存储路径后两个记忆目录不随迁、reload 后重建空目录导致记忆“消失”;现将其纳入存储子目录清单(迁移/清理自动覆盖,copyDirectory 递归复制 <hash>/ 子目录),getStorageStats 统计计入记忆目录大小与文件数,ensureDirectories 在生效路径下预建记忆目录,与「记忆数据随自定义存储路径迁移」的既有承诺一致。
修复编辑用户消息保存后候选切换器(‹ 2/2 ›)不立即显示:编辑分支流结束后分支图虽已刷新,但本地窗口中被编辑消息仍保留旧候选 id,buildCandidateGroupForNode 判定其为候选组非活跃成员返回 null,需切换会话再切回(loadHistory 重载后 id 与后端一致)才恢复;现 loadBranchGraph 刷新成功后把窗口内「候选组非活跃成员」的用户消息 id 对齐为图活跃候选(BR-01 原则:窗口 id 与后端主历史 Content.id 一致),保存后立即可见切换器,幂等不误伤其他路径;新增回归测试(editBranchRefresh.test.ts 第 5 用例:complete 后 id 对齐 + 候选组命中)。
后台任务状态条(BackgroundTaskBar)新增「清除已完成」按钮:一键清除所有已完成的后台任务 chip(运行中保留);若存在结果尚未汇报给模型的任务(回执未进入对话历史),先弹危险确认框提示再清除,避免静默丢失任务结果;按钮带可清除数量提示,三语 i18n 同步;新增 backgroundTaskStore.dismissCompletedTasks 单元测试(backgroundTaskDismiss.test.ts)。
子代理路径 ToolDeclarationResolver 监听器泄漏(H-1):每次 run 新建实例会向 McpManager 单例注册 3 个永久事件监听器且从不释放,重度多代理下监听器无界累积、MCP 事件派发退化为 O(n);改为按依赖引用共享实例(容量 4 LRU 淘汰),并新增 dispose() 释放监听器,同时让子代理路径真正享受声明缓存收益。
ConversationManager 节点 ID 反查缓存(nodeIdCache)失效覆盖不完整:invalidateCaches 定义了但从未被调用,saveContents / 全量重写 / 历史迁移 / 删除对话等写路径不失效缓存,工具循环内(拒绝工具调用等结构性变更后 300ms 窗口内)checkpoint 反查可能命中陈旧节点 id;现全部写路径统一走 invalidateCaches。
设置搜索下拉键盘导航滚动跟随:结果超出下拉可视高度时,↑/↓ 选中的高亮项现在会保持在可视区域内(scrollIntoView({ block: 'nearest' }))。
ContextTrimService.computeValidSuffixMap 反向扫描与正向 validateHistoryIntegrity 在乱序配对时语义不一致(L-5):functionResponse 出现在其配对 functionCall 之前(跨消息或同消息内)时,正向判孤儿(invalid)而旧反向实现误判 valid,裁剪后可能把乱序配对发给 API;修复为按「本消息内更早 part / 右侧消息 / 待左侧治愈」三分支精确匹配正向语义。
设置搜索跳转时序与状态残留(L-1/L-2):跳转定位改为按目标元素相对滚动容器偏移计算一次 scrollTo(避免 smooth 动画未推进时同步读 rect 的时序冲突、打断动画);跳转成功后清空搜索词,侧边栏恢复常态高亮;search.hint 词条投入使用(搜索框聚焦且未输入时显示提示)。
pendingWholeBuffer 注释修正(L-4):原注释声称「上限由硬限制保护」,实际代码对未知格式的整段累积没有大小上限(与改动前行为一致),已修正为准确描述。
新增回归测试:subagentResolverSharing.test.ts(监听器只注册一次 / dispose 释放 / 容量淘汰)、conversationMessageNodeId.test.ts 补写路径失效断言(仓储替换、删除对话)、contextTrimValidSuffixEquivalence.test.ts(computeValidSuffixMap 与正向校验逐候选等价 + 固定 seed 随机模糊对比 50 轮)、toolDeclarationResolverCache.test.ts(缓存命中 / 参数与设置指纹与 MCP 版本失效 / dispose)、settingsSearchAnchorConsistency.test.ts(SEARCH_INDEX 锚点与组件 data-search-anchor 一致性)、todoListIncremental.test.ts(增量重放参数分段一致性)与 computed.test.ts 增量分支(流式追加 / 尾消息原地更新 / 前缀替换回退 / 数组缩短 / 总结估算)。
全仓审计修复(PR #17 合并):①跨对话存档误删——CheckpointQueryService.removeOrphanBackupDirs 孤儿判定汇总全部对话的存档记录(有界并发枚举)+ manifest 身份守卫(fail-closed,含 manifest 的目录绝不删)+ 无 manifest 目录的 mtime 新鲜度守卫(创建中窗口跳过);②代理流式 buffer 偏移——ChannelManager 直接以 parseStreamBuffer 返回的 remaining 为下轮基线(修复 JSON-lines 逐行格式丢 chunk、SSE 事件跨 chunk 尾随空行切坏 data: 前缀);③总结消息插入窗口中间不可见/重复——insertMessageAt 中间插入清可见消息增量缓存;④StreamAbortManager:create() 替换控制器时释放 idleWaiters、waitForIdle 退休链等待超时后返回(修复 awaitConversationIdle → 后台回执永久挂起);⑤分支图冻结自愈——空占位超龄判定(10 分钟,isActiveEmptyPlaceholder):崩溃/被杀遗留的空 reroll/edit 占位不再让 append 跳过图同步(append 前先收敛、deferred 同步不再永久 defer),超龄幽灵占位软删回收;⑥SubAgent Monitor contentDelta:快照尾部为工具结果时追加新楼层而非覆盖上一轮模型消息(附 5 例回归测试);⑦分支:deleteToMessage 截断含总结走 summary_deleted 全量重建、edit 流程补写节点 contentMetadata、contentMetadata 排除 turnDynamicContext 快照、修复备份保留数上限;⑧安全:regexGuard 嵌套分组量词检测、SettingsCore 深合并过滤 __proto__/constructor/prototype、Memento 写队列串行化、FileSettingsStorage 原子写、McpManager 重连前断开旧 stdio、StdioClient 通知吞错、nodeIdCache epoch 守卫防陈旧回填;⑨前端:autoSummary chunk 不再被 streamId 门禁丢弃(含 tab 缓冲路径)、error 路径结束半截消息 streaming 标志、cancelled/error/本地取消重置 turnBaseTokens、WorkspaceRestoreGuard 等待旧流完全退出、thought 视图模式与裁剪提示按 messageId 持久化 + prune 集成、SummaryMessage 删除/恢复互斥、投递提示按 seq 精确移除。
regexGuard 增强(审查后优化):扫描式嵌套量词检测补「裸量词原子」跟踪(拦截 (?:a+|(?:ab))+、(?<name>a+)+ 等嵌套 + 原子量词形态,同时保持 (a+)?/(ab+)? 线性形态放行);正则启发式检测前净化转义序列与字符类(不再误伤 \(a+\\)+、([a+])+);范围量词只认可变 {n,}/{n,m}(定长 (a{2}){2} 放行);新增 10 组检测矩阵用例。
ConversationManager nodeIdCache epoch 改全局单调计数器 + LRU 容量淘汰(审查后优化):
计数器不归零、淘汰只删最旧条目,消除「整体清空后 0===0」与在途读盘回填的碰撞窗口。
modelList 缓存读写改浅拷贝一层(原 JSON 序列化深拷贝会丢失 undefined 字段且有全量序列化开销);FileSettingsStorage tmp 文件名加随机后缀(并发 save 不再互相踩)+ rename 失败清理残留;handleAutoSummary 标记起点下界钳制 Math.max(0, insertIndex - markedCount)(负起点会把窗口外消息全部误标记)。
diff 部分接受/部分拒绝时前端误显示「全部接受」(根因:apply_diff 返回的 appliedCount/failedCount 是创建 pending diff 前的初始匹配统计,用户块级拒绝后 DiffReviewSession outcome 为 partial 但映射为公共 accepted,前端仅凭计数判定部分成功):PendingDiff 新增 partial/rejectedBlockIndices,finalizeAcceptedDiff 在终结时统计被拒绝块写回;apply_diff 两条路径返回 status:'partial' 与修正后的计数(成功块-拒绝块 / 失败块+拒绝块),消息措辞区分 rejected(用户拒绝)与 failed/skipped(初始未匹配);前端 ToolMessage/apply_diff.vue 识别 partial → warning 并做块级 rejected 标记,三语 i18n 同步;5 处工具状态推导(streamingToolProgress、ChatFlowService×2、streamChunkHandlers、buildResponseViewerData、SubAgentMonitor、agentRun reducer)统一 partial 判定,消除流式/对话框/子代理与最终卡片状态不一致;DiffCodeLensProvider.updateBlockStatus 由数组下标改为按 block.index 定位(混合成败稀疏块下标时原实现静默 no-op);补回归测试(partial/rejectedBlockIndices、全接受反向护栏、userEditedContent、lens session 缺失、mock 泄漏修复)。
You can’t perform that action at this time.