-
released this
2026-07-22 15:18:21 +08:00 | 0 commits to master since this release版本概述
v0.3.18 是一次重要的稳定性与正确性修复版本,聚焦解决三个用户可感知的核心问题:
- 上下文压缩不触发:1M 上下文设置下,详情栏 token 用量到 100%、总计超 1M,但压缩从未触发
- 输入框永久显示"工具加载中":MCP 初始化竞态条件导致前端永久错过
tools:ready事件 - Token 显示语义错误:前端用累计消耗除以单次窗口得出无意义百分比
同时加固了记忆系统、SOUL.md 降级处理和 Token 估算准确度。
上下文压缩机制修复(核心)
问题现象
用户在 LLM 设置 1M 上下文时,观察到详情栏 token 用量"上下文"到了 100%、"总计"超过了 1M,但应用从未触发上下文压缩,最终导致 API 返回 413(请求体过大)。
根因分析
effectiveContextWindow缺少默认值:engine.ts压缩判断处this.config.contextLength ?? this.config.contextWindow没有?? 128_000兜底。若两者都为undefined,compressionThreshold = 0.8 * undefined = NaN,actualTokens > NaN永远为false,压缩永远不会触发。- 估算值偏低:
estimateMessagesTokens基于字符数估算,中文场景下估算值显著低于 LLM Provider 实际计费的 token 数。估算没到阈值不压缩,但真实 API 调用已经 413。 - 保留区固定条数不合理:
compressMessages保留最近 10 条消息,但不同上下文窗口下 10 条消息的 token 占用差异巨大。 - charsPerToken 偏差:二次截断使用
charsPerToken = 2(2 字符/token),对中文偏激进,实际中文约 1 字符/token。
修复内容
engine.ts新增lastRealInputTokens字段,记录 LLM 返回的真实输入 token 数- 压缩判断取
max(估算值, 真实值)作为实际占用,避免估算偏低导致漏压缩 - 添加
?? 128_000默认值保护,防止compressionThreshold变NaN compressMessages保留区从固定 10 条改为按 token 预算动态截断(50% 上下文窗口)- 二次截断
charsPerToken从 2 调整为 1.0,与CJK_TOKEN_RATIO一致 - 压缩后重置
lastRealInputTokens,避免跨迭代污染 - 每个
run开始时重置lastRealInputTokens
前端 Token 显示修复
问题现象
详情栏 Token 用量面板的"上下文"百分比与压缩判断逻辑完全脱节——前端显示 100% 时实际并未触发压缩。
根因
前端
usagePercent = totalTokens(累计消耗) / contextWindow(单次窗口)语义错误。totalTokens是所有轮次的累加值,除以单次窗口得出无意义的百分比。而压缩判断用estimateMessagesTokens(request.messages)看的是单次占用,两者完全脱节。修复内容
- 区分"累计消耗"和"上下文占用"两个语义
TokenUsage.tsx上下文占用百分比改用lastInputTokens(单次占用),而非累计totalTokens- 新增"压缩节省"行(绿色),仅当
lastCompressedSaved > 0时显示 - 进度条改用
inputPercent展示累计消耗的构成(输入/输出占比) - "总计"改为"累计消耗","上下文"改为"上下文占用",语义更清晰
agent-store.tsTokenUsage接口新增lastInputTokens和lastCompressedSaved字段,4 处初始值统一更新useAgentStream.tsusage事件中lastInputTokens替换不累加,compressed事件通过streamEvent接收savedTokenshandlers.tsonCompressed同时发 toast + streamEvent,解决"压缩触发但前端 token 显示不降"缺陷- 旧数据兼容使用
?? 0,保证历史会话加载不崩溃
MCP 工具就绪竞态修复
问题现象
应用启动后输入框永久显示"工具加载中",用户无法输入消息。
根因
mcpManager.initialize()内部的connectServer是异步且不await的,initialize()Promise 几乎立即 resolve,tools:ready事件在主进程启动后极快就发出。而前端ipcRenderer.on('tools:ready')监听器要等 React 组件挂载后才注册。事件在监听器注册前已发出,导致永久错过,toolsReady永远false,输入框永久禁用。修复内容
采用"事件 + 查询"双重机制解决竞态:
main.ts维护toolsReady标志,MCP 完成/失败时置true并广播事件main.ts注册tools:isReadyIPC handler,返回当前状态preload.ts暴露tools.isReady()查询方法App.tsx注册onReady监听器后立即调用isReady()查询当前状态- 无论事件是否错过,前端都能通过查询恢复正确状态
记忆系统加固
退出时异步固化数据丢失
- 问题:
MemoryConsolidator.consolidate是异步的,用户关闭应用时若固化未完成,记忆会丢失 - 修复:
consolidator.ts新增runningPromise跟踪 +waitForCompletion(35s)方法,main.ts的before-quit钩子等待固化完成,shutdown 超时从 5s 延长到 40s
双轨存储无交叉验证
- 问题:固化到
MEMORY.md的记忆不会写入semantic_memories表,两个存储源可能不一致 - 修复:
consolidator.ts注入MemoryManager,固化时同步写入semantic_memories表
中文分词优化
manager.tstokenize按中英文标点切分子句后再做 bigram,提升 TF-IDF 检索准确度- 清理正则表达式冗余括号
SOUL.md 降级处理
问题现象
SOUL.md 为空或不存在时,应用静默降级到默认身份,用户自定义人格丢失但不知情。同时若每次发消息都弹 toast 提示,会造成骚扰。
修复内容
context-builder.tsSOUL.md 为空或不存在时降级到默认身份,向前端发 toast 提示用户- 新增
fallbackRoleNotified去重标志,仅首次降级通知,避免每次发消息都弹 toast - SOUL.md 恢复内容时重置标志,下次再降级会再次通知
Token 估算调整
token-estimator.tsCJK_TOKEN_RATIO从 1.5 调整为 1.0,与实际中文 token 比例更接近
MCP 异步初始化
main.tsMCP 完成后广播tools:ready事件,前端 UI 据以控制输入框可用性preload.ts+global.d.ts暴露tools.onReady()监听器App.tsx+ChatInput.tsxtoolsReady状态控制输入框可用性
版本号
package.json+package-lock.json从 0.3.16 升级到 0.3.18
修改文件清单(16 个)
主进程(electron/)
harness/agent-loop/engine.ts— 压缩机制核心修复harness/memory/consolidator.ts— 退出保护 + 双写harness/memory/manager.ts— 中文分词优化harness/prompts/context-builder.ts— SOUL.md 降级harness/utils/token-estimator.ts— CJK 系数调整ipc/handlers.ts— onCompressed 双发main.ts— toolsReady 标志 + isReady handler + 退出等待preload.ts— 暴露 isReady 方法
前端(src/)
App.tsx— 竞态保护查询components/chat/ChatInput.tsx— toolsReady 控制输入框components/trace/TokenUsage.tsx— Token 显示重构hooks/useAgentStream.ts— 事件处理stores/agent-store.ts— TokenUsage 接口扩展types/global.d.ts— 类型声明
配置
package.json— 版本号package-lock.json— 版本号
验证
npm run typecheck通过(exit code 0)- 所有修改经过两轮代码复查,确认无引入新问题、无破坏原有功能
Downloads