Releases: jrcjrcc/deepseek-nyamu
Release list
使用新版本ui
Windows Sub-agent 完成时 UI 渲染宽度减半 Bug
Windows Sub-agent 完成时 UI 渲染宽度减半 Bug
概述
在 Windows 平台下,当 sub-agent(子代理)完成并返回数据给主 agent 后,终端 UI 的渲染区域会缩小到约原本宽度的一半,导致文字换行错位、内容错乱。必须手动缩小窗口再全屏才能恢复。
- 受影响平台:Windows(PowerShell / Windows Terminal)
- 不受影响:WSL (zsh)、macOS、Linux
- 触发命令:
/agent 0 今天天气怎么样等涉及 sub-agent 交互的场景
症状
| # | 症状 | 说明 |
|---|---|---|
| 1 | UI 渲染区域缩小到约 1/2 宽度 | 终端全屏,但内容只占一半宽度 |
| 2 | 文字换行错位 | 在错误的宽度上换行 |
| 3 | 滚动/刷新无效 | needs_redraw 触发重绘后仍然错误 |
| 4 | 缩小窗口时"贪吃蛇"效果 | 右侧渲染内容跨行穿到左侧 |
| 5 | 必须重新缩放窗口才能恢复 | 缩小→全屏→滚动后恢复正常 |
探索过程
第一阶段:初步定位
最初以为是 transcript cache 的 revision 未更新导致的布局缓存问题。检查了 ensure_split() 和 flatten_from() 的缓存逻辑。
已修复:在 subagent_routing.rs 中将 3 处 mark_history_updated() 改为 bump_history_cell(idx),强制 sub-agent 卡片所在 cell 的 revision 变化,使 cache 检测到 miss 并重建布局。
但问题仍未解决。
第二阶段:缩小范围
发现 WSL 下不触发、Windows 下必触发 → 平台相关。
排查方向:
ColorDepth::detect()— 新旧项目相同 ✅ColorCompatBackend— 新旧项目代码一致 ✅- 依赖版本 (crossterm 0.28, ratatui 0.30) — 相同 ✅
- 新旧项目的
ui.rs事件循环 — 几乎相同 ✅
第三阶段:发现关键线索
对比新旧项目 size() 方法:
旧项目 (nyamu):
fn size(&self) -> io::Result<Size> {
match self.forced_size {
Some(size) => Ok(size),
None => self.inner.size(),
}
}新项目:
fn size(&self) -> io::Result<Size> {
if let Some(size) = self.terminal_size.or(self.forced_size) {
return Ok(size);
}
self.inner.size()
}发现 set_terminal_size() 方法定义在 ColorCompatBackend 上,但整个代码库里没有任何调用者。terminal_size 始终为 None。
第四阶段:深入事件流程
跟踪 AgentComplete 事件处理器:
// ui.rs:2033
if should_recapture_terminal {
resume_terminal(...)?;
}对比 ResumeEvents 处理器:
// ui.rs:1957
if event_broker.is_paused() {
resume_terminal(...)?;
}发现 AgentComplete 缺少 is_paused() 守卫!
第五阶段:完整触发链确认
AgentComplete
→ should_recapture_terminal = true
→ resume_terminal() 无条件调用(缺少 is_paused 守卫)
→ EnterAlternateScreen(已在 alt screen 中时二次进入)
→ Windows 创建新的 alt screen buffer,尺寸可能 ≠ 窗口尺寸
→ 下一次 draw()
→ backend.size()
→ terminal_size = None(从未被设置)
→ forced_size = 可能过期或 None
→ crossterm::terminal::size() 返回 buffer 宽度(非窗口宽度)
→ Frame 使用错误宽度渲染
→ 内容换行在错误宽度 → "半宽"UI
根因分析
Bug 由三个因素共同导致:
因素 A:AgentComplete 缺 is_paused() 守卫
AgentComplete 在 sub-agent 完成时无条件调用 resume_terminal(),即使终端从未被暂停过(sub-agent 使用非交互式工具,不会触发 PauseEvents)。
而同一文件中的 ResumeEvents 处理器就有 event_broker.is_paused() 守卫。
位置:ui.rs:2033-2044
因素 B:二次 EnterAlternateScreen 导致缓冲区尺寸漂移
Windows 上,已在 alternate screen 中时再次调用 EnterAlternateScreen 会创建新的 alt screen 缓冲区。新缓冲区的 dwSize.X(从 GetConsoleScreenBufferInfo() 获取)可能与实际窗口宽度不一致。
而 resume_terminal() 后没有重新查询终端尺寸。
位置:ui.rs:7547-7548(resume_terminal 中的 EnterAlternateScreen)
因素 C:set_terminal_size() 从未被调用
ColorCompatBackend 定义了 set_terminal_size() 方法和 terminal_size 缓存字段,但没有任何代码调用它。size() 方法中 terminal_size.or(forced_size) 的回退链中 terminal_size 总是 None,实际只依赖 forced_size(可能过期)或实时 crossterm::terminal::size()(Windows WinAPI 返回 buffer 宽度而非窗口宽度)。
位置:color_compat.rs:89, 180-185
修复方案
修复 1:AgentComplete 加 is_paused() 守卫
// 修复前
if should_recapture_terminal {
resume_terminal(...)?;
}
// 修复后
if should_recapture_terminal && event_broker.is_paused() {
resume_terminal(...)?;
}作用:只在终端确实被暂停过时调用 resume_terminal(),防止 sub-agent 完成时二次 EnterAlternateScreen。
修复 2:resume_terminal 后重新查询并缓存终端尺寸
// resume_terminal() 末尾添加
if let Ok((cols, rows)) = crossterm::terminal::size() {
terminal.backend_mut().set_terminal_size(Size::new(cols, rows));
}作用:重新进入 alt screen 后把当前真实终端尺寸缓存到 terminal_size,后续 draw() 使用缓存值避免过期的 forced_size 或 WinAPI 的 buffer 宽度。
修复 3:Resize 处理器也设置 terminal_size
// 修复前
backend.force_size(Size::new(final_w, final_h));
// 修复后
let new_size = Size::new(final_w, final_h);
backend.force_size(new_size);
backend.set_terminal_size(new_size);作用:Resize 事件时同时设置 forced_size 和 terminal_size,使 size() 方法的第一级缓存真正生效。
最终改动清单
| 文件 | 行 | 改动 |
|---|---|---|
ui.rs |
2033 | should_recapture_terminal → should_recapture_terminal && event_broker.is_paused() |
ui.rs |
2557-2565 | resize handler 添加 set_terminal_size(),移除 clear_forced_size() |
ui.rs |
7562-7570 | resume_terminal() 末尾添加 crossterm::terminal::size() 查询和 set_terminal_size() 缓存 |
color_compat.rs |
— | set_terminal_size() 方法已定义,无需改动 |
subagent_routing.rs |
116, 132, 155 | mark_history_updated() → bump_history_cell()(补充修复) |
平台安全性
| 平台 | 风险 | 说明 |
|---|---|---|
| macOS | ✅ 无影响 | is_paused() 所有平台可用;TIOCGWINSZ 始终返回正确尺寸 |
| Linux | ✅ 无影响 | 同上 |
| WSL | ✅ 无影响 | 同上 |
| Windows | ✅ 已修复 | 三次改动共同防止 buffer/窗口尺寸漂移 |
致谢
- 排查工具:CodeWhale + Rust 源码分析
- 对比基准:
deepseek-nyamu-master(喵梦.exe) - 测试环境:Windows Terminal + PowerShell, WSL2 + zsh
deepseek-nyamu v0.8.46-nyamu1
deepseek-nyamu v0.8.46-nyamu1
基于 CodeWhale v0.8.46 的深度定制分支,本次更新聚焦于安全加固与稳态性能提升。
🔒 安全修复
- 计时攻击防护:app-server auth token
==短路比较替换为subtle::ConstantTimeEq::ct_eq()常数量级按位比较 - XSS 防护:Mermaid 组件手写 regex sanitizer 替换为
DOMPurify.sanitize(),覆盖 data: URL、foreignObject 嵌入、SVG use 劫持等 regex 无法穷尽的 edge case - Token 泄露防护:auth token 不再通过
eprintln!输出到 stderr,防止泄露到日志文件 - 锁中毒级联崩溃防护:7 处
std::sync::Mutex::lock().unwrap()改为unwrap_or_else(|e| e.into_inner()),一线程 panic 不再崩全进程 - serde 静默错误传播:3 处
unwrap_or(Value::Null)改为map_err向上传播,数据损坏不再被静默吞掉 - MCP 子进程隔离:Windows Job Object / Linux PR_SET_PDEATHSIG + atexit handler + shutdown 链三路保障,覆盖 panic=abort、SIGKILL、OOM killer、process::exit 全部异常退出路径
⚡ 性能优化
- Sub-agent 通知机制:
tokio::sync::Notify替代 250ms 轮询 + RwLock 读锁争用,并发 N 个 agent 的锁竞争从 O(n) 降到 O(1),cancel 写锁立等可取 - SSE 流式空闲超时:默认 300s→60s,config.toml 三级注入配置,keepalive 空 chunk 不再重置计时器,防静默挂起
- 通道背压降采样:
try_send()替代send().await,满时丢弃瞬时状态事件(MessageDelta/Status),engine 不再因通道满而停转 - SQLite 并发加固:WAL 模式 + busy_timeout=5000 + per-connection PRAGMA 配置,200 并发写入零 SQLITE_BUSY
- APPROVAL_TIMEOUT 可配:硬编码 300s 改为
OnceLock+ 环境变量CODEWHALE_APPROVAL_TIMEOUT_SECS覆盖
🧰 其他改进
- 30 个
&指令(记忆/项目分析/代码编辑/会话/文档/实用工具) !<command>Bash 直通- 完整汉化(280+ MessageId)
- 主题颜色撞色修复
- Dream RAG 缓存优化 + Microcompact 折叠
- 默认模型
deepseek-v4-pro→deepseek-v4-flash
nyamu fork v0.8.46 — 缓存优化 + 微压缩 + 新指令系统
nyamu fork v0.8.46 — 缓存优化 + 微压缩 + 新指令系统
💾 缓存命中率大幅提升(核心优化)
系统 prompt 的 ~82K 前缀缓存从"每轮全 miss"修复为"每轮全 hit":
- Dream RAG 时间戳修复:去掉 manifest 中的动态时间戳
<!-- loaded at {now} -->,改为<!-- byte-stable across turns -->。系统 prompt 从每轮变化 5+ 秒恢复为完全稳定 - Manifest mtime 缓存:添加
ManifestCache,按max_mtime + file_count做 cache key。记忆文件不变时输出字节完全相同,前缀缓存跨轮命中
收益:每轮节省 ~30K cache-miss → cache-hit 转换。长期会话的缓存命中率从 ~60% 提升至 ~90%+。
⚡ 微压缩算法(Microcompact)
借鉴 Claude Code 的 microcompact 管线,适配 DeepSeek API:
- 位置:发送 API 请求前自动执行,无感运行
- 触发:可压缩 tool result 超过 5 个时,自动折叠最旧的结果
- 处理:保留最近 5 个完整内容,更旧的替换为折叠标记 +
retrieve_tool_result路径引用 - 可压缩工具:read_file, exec_shell, grep_files, file_search, web_search, fetch_url, list_dir
- 与 Spillover 互补:Spillover 管单个结果 > 100 KiB 的体积,Microcompact 管累积结果的数量,两者互不冲突
收益:请求 body 缩小 30-50%,重建缓存更快更便宜。
🆕 新功能
| 功能 | 说明 |
|---|---|
!<command> bash 直通 |
用户输入 ! 前缀直接执行 Shell 命令,不经过 AI。适合 git push、npm install、gcloud auth login 等交互式或用户想自己控制的命令。输出 10K 字符上限,超长自动截断。 |
&bak |
递归备份工作区所有代码文件(40+ 扩展名,跳过 .git/node_modules/target),自动跳过已有 .bak |
&rmb |
删除所有 <name>.<ext>.bak 备份文件 |
&¥ |
直调 DeepSeek /user/balance API 查询余额,支持 DEEPSEEK_API_KEY 环境变量 |
🔧 其他改进
- 默认模型改为
deepseek-v4-flash(+reasoning_effort = max),日常响应更快 - 添加
academic-paper-pipeline默认 skill - & 命令三处注册入口统一(帮助面板 / 命令面板 / 速选栏)
- 修复
!前缀支持无空格写法(!hello和! hello均可)
Full Changelog: v0.8.46...v0.8.46-nyamu
基于 CodeWhale v0.8.46 的定制分支
基于 CodeWhale v0.8.46 的定制分支
