# Clavue v8.12.1 — Release Notes

**发布日期**:2026-05-19
**npm**:`clavue@8.12.1`
**GitHub**:https://github.com/mycode699/clavue/releases/tag/v8.12.1
**升级命令**:

```bash
npm install -g clavue@8.12.1
# 或一次性:
npx -y clavue@8.12.1
```

---

## TL;DR

修一个老 bug:**heap_used 在 critical 阈值附近震荡时,autoCompact 会无限重压**(中位间隔 ~120s,最短 52s)。8.12.1 给 critical-heap 路径加 60s wall-clock 节流,跨节流期由 `query.ts` 的 heap soft-block + GC 接管。

测试基线:**1496 pass / 0 fail / 6 skipped**(v8.12.0 基线 1489 + 7 新测试)。

升级风险:**低**。同等 heap 状态下行为单调更宽松——critical 触发只会更少不会更多;真 OOM 前依然会触发(节流过期后)。无配置变更。

---

## 1. 现象

某用户 34MB / 8780 行的会话日志显示:

- 93 次 auto compact + 2 次 emergency truncate。
- 86 次 compact 触发时 `preTokens < 70K`,远低于 token 阈值。
- 5/18 16:01 后中位 compact 间隔 119s,最短 52s。
- 每次 compact 后 heap 几乎不下降——V8 会保留已分配的 page;真正的内存回收靠 major GC,不靠丢消息。

## 2. 根因

`src/services/compact/autoCompactDecision.ts` 的 critical-heap 分支:

```ts
// 旧:8.12.0 及之前
if (isAboveCriticalHeapThreshold) {
  return { shouldCompact: true, nextPostCompactionState: null }
}
```

直接 return,既绕过 token 阈值,也绕过 `POST_COMPACTION_RETRIGGER_GROWTH_TOKENS` 冷却。一旦 heap_used 卡在 critical 线上下震荡,每个 turn 都会再压一次,但又压不下去。

## 3. 修复

`decideAutoCompactFromUsage` 增加可选 `now` + `minCriticalIntervalMs`(默认 `CRITICAL_COMPACT_MIN_INTERVAL_MS = 60_000`)。`PostCompactionAutoCompactStateSnapshot` 增加可选 `lastCriticalCompactAt: number | null`,critical 触发时打时间戳:

```ts
if (isAboveCriticalHeapThreshold) {
  if (
    lastCriticalAt !== null &&
    nowMs - lastCriticalAt < minCriticalIntervalMs
  ) {
    // 节流命中:保留 state,让 query.ts:704 的 heap soft-block + GC 接管
    return { shouldCompact: false, nextPostCompactionState: postCompactionState }
  }
  return {
    shouldCompact: true,
    nextPostCompactionState: { baselineTokenCount: null, lastCriticalCompactAt: nowMs },
  }
}
```

关键不变量:
- 节流命中时**不更新** `lastCriticalCompactAt`,避免高频 critical 探测把时钟一直推后,饿死真正过期的触发。
- 非 critical 路径(token 阈值 / 普通 heap 阈值)完全不受影响,保留原有冷却逻辑。
- 函数签名向后兼容:`now` / `minCriticalIntervalMs` 都是可选参数,既有调用点零改动。
- 状态字段也向后兼容:`lastCriticalCompactAt` 可选,只在确有值时附加,既有 `deepEqual` 测试不破。

`autoCompact.ts:271` 的 critical 短路改为根据 `decision.shouldCompact` 区分日志,行为靠纯函数侧的节流来决定。

## 4. 测试

新增 `tests/auto-compact-critical-cooldown.test.mjs`(7 个),覆盖:

- 首次 critical → trigger,正确打时间戳。
- 60s 内再次 critical → skip,state 不被破坏(包括时间戳不更新)。
- >60s 后再次 critical → trigger,re-stamp。
- `minCriticalIntervalMs: 0` 注入下立即放行(测试逃生口)。
- 非 critical token-pressure 路径不受影响。
- critical→非 critical baseline 写入时,`lastCriticalCompactAt` 跨阶段保留。
- `CRITICAL_COMPACT_MIN_INTERVAL_MS === 60_000`。

`tests/auto-compact-retrigger.test.mjs` 第 2 个用例做了相应更新(critical 触发现在带 timestamp,但 `baselineTokenCount` 仍清零,语义不变)。

```
node --test tests/auto-compact-critical-cooldown.test.mjs \
            tests/auto-compact-retrigger.test.mjs \
            tests/pinned-context.test.mjs \
            tests/micro-reflection.test.mjs \
            tests/emergency-truncate.test.mjs \
            tests/heap-soft-block-contract.test.mjs
# 40 pass / 0 fail
```

全测:`npm test` → **1496 pass / 0 fail / 6 skipped**。

## 5. 升级建议

- 默认升级即可,**无配置变更**。
- 仍受 `CLAVUE_HEAP_AUTOCOMPACT_PCT` / `CLAVUE_HEAP_CRITICAL_PCT` 控制(critical 路径触发阈值未改)。
- 若你之前观察到「会话上下文反复消失」「短时间内被 compact 多次」,8.12.1 应直接缓解。
- 真 OOM 前依然会触发(节流过期后);节流期间靠 `query.ts` heap soft-block + `tryForceMajorGc`。
- 撤回:回退到 `clavue@8.12.0`(`npm install -g clavue@8.12.0`)。
