oh-my-pi v15.11.3 发布 —— 以前会话结束记忆就清零,现在真的能存住了
如果答案是「什么都没记住」,不是你理解错了,是记忆合并就没跑过。
v15.11.3 的改动不是那种能截图秀出来的新功能。它是一轮针对「默默工作但不工作」的底层 bug 清扫:记忆合并从来不跑、LSP 格式化强塞 tabSize:3、multi-path 搜索塌缩成全局扫描。每个 bug 单独看都哑巴吃黄连,合在一起就是「总觉得哪里不对但又说不上来」的开发体验。

以前:记忆合并只在 /memory enqueue 时才跑
oh-my-pi 的 memory 系统有一个完整的管线:新信息提取 → 转化为 gist/triples → 合并进入 session 记忆 → 最终落盘。这套逻辑没问题,问题在于触发时机。
MnemopiSessionState.dispose() ——也就是 session 关闭时的清理方法——从未执行过记忆合并流程。它只调用了关闭句柄,但没有 drain pending extractions,没有触发 sleepAllSessions。结果就是:episodic_memory、gists、consolidation_log、graph_edges、triples——整张记忆表——在任何非手工触发的场景下都是空的。
如果你从没用过 /memory enqueue 这个斜杠命令,那你的 oh-my-pi session,某种意义上,从来没有「记住」过任何东西。每次新对话都得重头再来,没有跨 session 积累。
这个 bug 的藏匿方式很聪明:它不报错。你不查 episodic_memory 表,永远不知道它没被写入。甚至 /memory enqueue 都能正常跑——甚至你跑了之后它确实能存上——但就是不在 session 关闭时自动触发。所以正常运行的使用者,认为"系统在工作"的那个路径,就断了。
修复很简单:dispose() 现在在关闭句柄之前调用 consolidate()——drain pending extractions、sleepAllSessions、处理所有共享 bank。[#2320]
顺便说一句,/memory clear 不受影响——mnemopiBackend.clear 传入 dispose({ consolidate: false }),清空时不花 tokens 做合并,因为马上要 wipe。而 /memory enqueue 从子 agent 调用时也不会被 aliasOf 短路——consolidate() 本身不检查 alias,alias 守卫在 dispose 生命周期层。所以正常的 session 关闭会合并,手动的 enqueue 也会合并,毁灭性的 clear 不会浪费 tokens。
以前:LSP 格式化强塞 tabSize:3
这是个特别有意思的 bug。它影响面不算大——只影响用了 lsp.formatOnWrite 的用户——但一旦遇上,非常隐蔽又令人抓狂。
v15.11.3 之前,lsp.formatOnWrite 对每一次 textDocument/formatting 请求发送的都是硬编码的 tabSize: 3, insertSpaces: true。不是你的编辑器配置——是代码里写死的常量。这意味着什么?你在一个 2 空格缩进的 YAML 文件上按保存,yaml-language-server 收到格式化请求,格式选项是「3 空格,用空格」——于是你的整个 YAML 文件被重新缩进了。indent_size: 2 变成 indent_size: 2——不是审美问题,是格式被静默改变了。更糟的是,两份相同的常量 DEFAULT_FORMAT_OPTIONS 散落在 lsp/index.ts 和 lsp/clients/lsp-linter-client.ts 里,改一个另一个不同步。
现在格式选项按 .editorconfig 解析——indent_size、indent_style、tab_width 逐个查询。如果项目没有 .editorconfig,fallback 到对 in-memory 内容的缩进嗅探(模型正要写入的那个文本的缩进模式)。如果还嗅不出来,最后回退到 2 空格默认。不再是 3。[#2329]
以前:多路径搜索塌缩成全局扫描
search 工具支持传入多条路径:paths: [".", "~/.gitconfig"]。逻辑上,这应该把 . 和 ~/.gitconfig 作为两条独立的作用域分别扫描。
但实际上,之前的行为是把它们折叠到共同祖先路径上做一次 glob 扫描。如果当前目录是 /Users/me/project,而 ~/.gitconfig 是 /Users/me/.gitconfig,共同祖先是 /Users/me——于是扫描了整个 /Users/me 目录,直到 grep 的 30 秒超时触发。~/.gitconfig 这个文件条目本身,在 walker 已经剪枝过的目录(比如被 gitignored 的路径)下,永远匹配不到,因为被折叠到了目录 glob 的并集里。
现在多路径散射成每个路径独立扫描。普通文件条目直接作为自己的目标读取。重叠路径的匹配结果去重。30 秒超时不再是因为搜索 .gitconfig 而扫描了整个 home 目录。[#2329]
Working loader 不再留下假历史行
TUI 用户应该见过这个:你在等模型回复,状态行显示 "Working…",然后模型出结果了,但你往回翻,发现那行 "Working…" 被钉在了历史里——它本应是实时更新的占位符,却被当作了固定消息提交。
原因是 loader 组件在 useEffect 卸载时,会同步它的最终内容到 scrollback 上游。但如果主 transcript 也在同时提交内容,loader 的 commit 可能覆盖 transcript 的边界,导致 transcript 的可变行被当作「已完成」提交,留下假的历史行。[#2328]
修复方法很简洁:root compose 现在确保「顶层的 live seam」决定 commit 边界,而不是由第一个卸完的 loader 说了算。TUI 的交互状态容器只在它有 mounted 内容时才注册 seam。
Copilot 模型管理的几个更新
pi-catalog 这边的改动集中在 Copilot 模型变体的处理上。现在当 Copilot 有阶梯 token 定价时(base token + long-context 加价),系统会合成 -1m 后缀的模型变体。requestModelId 代表本地变体指向的上游真实模型 ID。Copilot discovery 现在请求 API 版本 2026-06-01。非 chat 模型的 /models 条目被过滤掉了,支持 vision 的模型自动启用 image input。mapModel 返回 null 时会跳过该条模型,而不是 fallback 到默认值。[#2328][#2329]
升级
# macOS / Linux(推荐一键安装)
curl -fsSL https://omp.sh/install | sh
# Homebrew
brew install can1357/tap/omp
# Bun(推荐)
bun install -g @oh-my-pi/pi-coding-agent
# Windows PowerShell
irm https://omp.sh/install.ps1 | iex
# Pinned 版本(mise)
mise use -g github:can1357/oh-my-pi
完整更新日志:GitHub Release
📢 关注我,获取最新开源更新日志和开发者工具动态。如果觉得这篇文章有用,欢迎转发给身边在探索 AI 编程工具的开发者朋友。
#oh-my-pi #AI编程 #记忆持久化 #LSP #开源工具