Repository navigation
fix: hard-enforce protection of recent messages + last user message - #5
Merged
Merged
Conversation
The recent-N / last-user-message protection existed only as advisory: computeProtectedRefs fed the protected set into recommendNode/nudge to shape which ranges are SUGGESTED as compressible. But applyCompression (the actual execution path, invoked by the compress tool) never consumed that set — the protectedMessageIds param was threaded through applySingleRange but never read. So a model could ignore the nudge and call compress() directly on the last N messages or the most recent user message, and it would succeed, compressing away the active task context. Two fixes: 1. applySingleRange now HARD-enforces protection: after computing the range's direct message ids, it converts them to refs (via state.messageRefs.byRaw — the protected set holds REF ids mNNNNN while filteredIds holds RAW ids) and rejects the range if any fall in the protected zone, throwing a clear error naming the offending refs. 2. applyCompression defaults protectedMessageIds to computeProtectedRefs(...) when the caller omits it, so every code path is safe by default — not just callers (like pai-acp's compress-tool) that remember to pass it. Also tightened Rule 3 semantics: the "last visible user message is always protected" now follows preserveRecentMessages > 0, coupling it to the same switch as Rule 1. Previously it was unconditional, which conflicted with tests/intent that set preserveRecentMessages=0 to fully opt out. Production defaultConfig uses 5, so the last user message remains always-protected in practice; this only affects callers that explicitly disable recent protection. Added tests/protect-recent.test.ts (5 cases: last-N refused, overlap refused, last-user refused even outside window, older range still allowed, default protection without explicit set). Updated existing tests whose small fixtures compressed the tail to opt out via preserveRecentMessages:0. typecheck clean; 187 tests pass; build OK.
This was referenced Sep 30, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
最近 N 条消息(
preserveRecentMessages)和最后一条用户消息的"保护"只在建议层生效,没有硬强制:computeProtectedRefs算出保护集 → 喂给recommendNode/nudge→ 只决定 nudge 建议哪些范围可压缩applyCompression(模型调 compress 工具的真正执行路径)从不消费这个保护集 ——protectedMessageIds参数一路传到applySingleRange却从未被读取(grep 0 次使用)后果:模型若无视 nudge 建议,直接调
compress({startId, endId})压缩最近 N 条或最后一条用户消息,会成功,把当前任务的活跃上下文压没。修复
1.
applySingleRange硬强制保护(src/compress.ts)计算范围直接消息后,把它们转成 ref(保护集存的是 REF id
mNNNNN,而filteredIds是 RAW id,需经state.messageRefs.byRaw转换)逐个测试是否在保护集,命中即抛错并点名具体 ref:2.
applyCompression默认安全(src/compress.ts)调用方没传
protectedMessageIds时,自动用computeProtectedRefs(...)计算,保证任意代码路径都安全——不只记住传参的调用方(如 pai-acp compress-tool)。3. Rule 3 语义收紧(
src/recommend.ts)"最后一条用户消息始终保护"现跟随
preserveRecentMessages > 0,与 Rule 1 同一开关。原先无条件保护与"设preserveRecentMessages=0完全 opt out"的意图冲突。生产defaultConfig用 5,实际始终保护;仅影响显式设 0 的场景(单元测试)。测试
新增
tests/protect-recent.test.ts(5 例):preserveRecentMessages > 0时)protectedMessageIds时默认保护仍生效更新了若干既有测试:小数据集 fixtures 压缩尾部的,显式
preserveRecentMessages: 0opt out;两个原依赖"Rule 3 无条件保护"契约的测试改为> 0触发。验证
安全影响
这是真实的数据安全缺陷修复——理论上模型一个错误/越界的 compress 调用就能吞掉正在进行的任务上下文。修复后,无论模型如何调用 compress,最近 N 条和最后一条用户消息都绝不可能被压缩。