---
name: compact-context
description: Help the user compact their session while keeping recent conversation turns intact. Use when the user asks to compact, says "局部compact", wants to free up context, or says "压缩一下但别把最近的聊天压掉". When invoked, silently run the steps below and only show the user the friendly messages marked with >.
---

# 局部压缩助手

**执行规则：所有操作静默完成，只把 > 引用块的内容说给用户。不提及脚本、命令、路径。必须严格绑定当前聊天线程；拿不到当前 session 时直接报错，不允许退化到“最近更新的会话”。**

本 SKILL.md 所在目录下有 `scripts/set-keep-recent.mjs`。用以下方式定位脚本（赋值给变量后使用，不展示给用户）：

```bash
_S=$({
  find . -maxdepth 8 -name "set-keep-recent.mjs" 2>/dev/null
  find "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/skills" -maxdepth 4 -name "set-keep-recent.mjs" 2>/dev/null
  find /home/node/.openclaw/skills -maxdepth 4 -name "set-keep-recent.mjs" 2>/dev/null
  for _d in /home/node/.openclaw/*/skills; do find "$_d" -maxdepth 4 -name "set-keep-recent.mjs" 2>/dev/null; done
} | head -1)
[ -z "$_S" ] && echo "ERROR: set-keep-recent.mjs not found" && exit 1
```

静默拼出当前线程的 selector 参数（优先 key，其次 id）：

```bash
_SESSION_ARGS=()
if [ -n "${OPENCLAW_TASK_SESSION_KEY:-}" ]; then
  _SESSION_ARGS=(--session-key "$OPENCLAW_TASK_SESSION_KEY")
elif [ -n "${OPENCLAW_TASK_SESSION_ID:-}" ]; then
  _SESSION_ARGS=(--session-id "$OPENCLAW_TASK_SESSION_ID")
else
  echo "ERROR: current session selector missing" && exit 1
fi
```

---

## 步骤零：首次使用检测（每次调用时静默检查）

静默检查 `~/.agents/AGENTS.md` 是否包含 `## Session Startup`：

```bash
grep -q "## Session Startup" "$HOME/.agents/AGENTS.md" 2>/dev/null
```

**如果不存在**，先找到 `setup.sh` 并静默运行：

```bash
_SH=$({
  find . -maxdepth 8 -name "setup.sh" -path "*/compact-context/*" 2>/dev/null
  find "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/skills" -maxdepth 5 -name "setup.sh" 2>/dev/null
  find /home/node/.openclaw/skills -maxdepth 5 -name "setup.sh" 2>/dev/null
} | head -1)
[ -n "$_SH" ] && bash "$_SH" >/dev/null 2>&1
```

运行完后对用户说：

> 🛡️ 已自动开启压缩恢复保护（首次配置）：压缩后会先补读任务上下文，再继续回复。

**如果已存在**，跳过此步骤，直接进入步骤一。

---

## 步骤一

静默执行 `node "$_S" --info --require-current-session "${_SESSION_ARGS[@]}"`，从输出解析 `turns=X used_pct=Y source=S`。

**如果 `source=native`**，对用户说：

> 📊 当前对话概况：
> - 总共聊了 **X 轮**
> - 占用了约 **Y%** 的上下文空间
>
> 你想尽量完整保留最近几轮？（比如"最近3轮"或"最近5轮"）
> 底层会按 token 预算保留最近几轮，其余更早内容会压缩成摘要。
> 先说明一下：压缩后系统提示里的 token 数字，表示保留下来的**消息估算体积**；真实 prompt 通常会更高，并以下一次真实调用 usage 为准。

**如果 `source=assistant-usage` 或 `source=transcript-estimate`**，对用户说：

> 📊 当前对话概况（估算）：
> - 当前可解析到约 **X 轮** 对话
> - 当前上下文占用约 **Y%**
>
> 暂时没拿到原生上下文统计，以上是按当前会话估算的结果。
> 先说明一下：压缩后系统提示里的 token 数字，表示保留下来的**消息估算体积**；真实 prompt 通常会更高，并以下一次真实调用 usage 为准。
> 你想尽量完整保留最近几轮？（比如"最近3轮"或"最近5轮"）
> 底层会按 token 预算保留最近几轮，其余更早内容会压缩成摘要。

---

## 步骤二

用户说"最近 N 轮"后，静默执行 `node "$_S" --turns <N> --require-current-session "${_SESSION_ARGS[@]}"`。脚本内部会额外补偿当前交互带来的 2 轮占位，但这是内部实现细节；对用户展示时始终用用户请求的 N 轮。从输出解析 `display_keep_turns`、`display_compress_turns`、`source`，然后对用户说：

> ✅ 准备就绪！
>
> 压缩后的效果：
> - 🗜️ **前 {display_compress_turns} 轮** → 压缩成一段摘要（保留关键信息）
> - 📌 **最近 {display_keep_turns} 轮** → 尽量完整保留
> - 📏 这次保留部分的**消息估算体积**会按脚本计算；真实 prompt 大小会在下一次实际调用后更新
>
> 局部压缩模式已设置好，现在请发送 `/compact` 触发执行。
> 这次的 /compact 是**局部压缩**，不是全量——发出去就会按上面的方案执行。
> （如果有特别想保留的信息，可以在后面加说明，比如 `/compact 重点保留XXX任务的进展`）

如果当前 `source` 不是 `native`，再补一句：

> 这次保留范围是按当前会话估算的 token 预算计算出来的；显示的消息估算和后续 session/store 里的真实 prompt 数字不是同一口径，不能直接对比。

---

## 步骤三

用户收到 `⚙️ Compacted ...` 后，静默执行 `node "$_S" --restore --require-current-session "${_SESSION_ARGS[@]}"`，然后对用户说：

> （已压缩）
> - 前 {display_compress_turns} 轮对话已压缩为摘要
> - 最近 {display_keep_turns} 轮已按预算保留
> - 本次压缩后保留消息估算约为 **{message_estimate_after}**
> - 真实 prompt 大小会在下一次实际调用后更新，届时 session/store 数字才有参考意义
> - 上下文空间已释放，可以继续工作了 ✓

如果拿不到 `{message_estimate_after}`，改为：

> （已压缩）
> - 前 {display_compress_turns} 轮对话已压缩为摘要
> - 最近 {display_keep_turns} 轮已按预算保留
> - 系统刚显示的 token 数字表示保留下来的消息估算体积；真实 prompt 仍以下一次实际调用 usage 为准
> - 上下文空间已释放，可以继续工作了 ✓

如果 `--restore` 返回 session mismatch，改为对用户说：

> 当前线程和之前设置局部压缩时的线程对不上，我没有自动还原设置，避免误改到别的会话。
