# pi-tmux-fork

[![npm version](https://img.shields.io/npm/v/pi-tmux-fork.svg)](https://www.npmjs.com/package/pi-tmux-fork)
[![npm downloads](https://img.shields.io/npm/dt/pi-tmux-fork.svg)](https://www.npmjs.com/package/pi-tmux-fork)
[![license](https://img.shields.io/npm/l/pi-tmux-fork.svg)](./LICENSE)
[![GitHub](https://img.shields.io/badge/GitHub-geeyu%2Ftmux--fork-181717?logo=github)](https://github.com/geeyu/tmux-fork)

[pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) 扩展，在 tmux 中 fork 当前会话，让子会话继承完整的对话历史，并复用父会话已建立的 prompt 缓存，避免重复 prefill。

## 安装

```bash
pi install npm:pi-tmux-fork
# 或从 GitHub 安装：
pi install git:github.com/geeyu/tmux-fork
```

> 安装后无需额外配置，重启 pi 即可在 tmux 会话内使用 `/tmux-fork*` 命令。

## 特性

- **会话 fork：** 完整复制父会话的 system prompt 与历史轮次，发送给 LLM 的 prompt 前缀字节级一致。
- **缓存复用：** 对于 GLM-5.2 等隐式缓存模型，子会话能直接命中父会话的 prompt 缓存，省去重复 prefill 计费（实测命中率从 0.4% 提升到 95%）。
- **worktree 隔离：** `/tmux-fork-gt` 在独立的 git worktree 中打开子 agent，文件改动互不干扰，同时通过 `cwd-cache-fixer` 保留缓存。
- **安全清理：** `/tmux-fork-gt-clean` 交互式列出并删除 worktree，自动检测进程占用，避免误删正在使用的目录。

## 前置要求

- 已安装 [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)，且 `pi` 命令在 `PATH` 中。
- 运行在 [tmux](https://github.com/tmux/tmux) 会话内（环境变量 `TMUX` 存在）。
- 当前会话已持久化（非 ephemeral 临时会话），否则没有可 fork 的 session 文件。
- `/tmux-fork-gt` 还要求当前目录位于某个 git 仓库内。

## 命令

### `/tmux-fork`

fork 当前会话，在 tmux 新窗口或分屏中打开一个全新的 pi 子会话。子会话继承全部对话历史，prompt 前缀与父会话一致以复用缓存。

```
/tmux-fork
```

无需参数。执行后会根据配置选择「整页新窗口」或「当前窗口分屏」打开子会话。

> **缓存接力：** 若当前会话本身是 `/tmux-fork-gt` 的后代（worktree 会话），`/tmux-fork` 会把 `PI_CACHE_CWD` 与 `cwd-cache-fixer` 扩展接力给孙会话，使其 system prompt 中的 cwd 仍固定为原仓库根，保持缓存一致。孙会话仍在同一 worktree 中操作，隔离性不变。

### `/tmux-fork-gt`

fork 当前会话，并在独立的 git worktree 中启动子 agent。子 agent 的文件操作只影响 worktree，与主工作区隔离；同时通过 `cwd-cache-fixer` 扩展把 system prompt 中的 cwd 固定为原仓库根，保留 prompt 缓存。

```
/tmux-fork-gt <简要任务描述>
```

| 参数 | 说明 |
|------|------|
| `<简要任务描述>` | 任务的一句话描述，会作为子 agent 的首条用户消息传入，必填 |

**示例：**

```
/tmux-fork-gt 把登录页的表单校验抽成独立组件
```

执行流程：

1. 解析 git 根目录，计算下一个 worktree 编号（`gittree-<N>-task`）。
2. 确保 `.worktrees/` 已被 `.gitignore` 忽略。
3. 在 `.worktrees/gittree-<N>-task` 创建 git worktree（编号冲突时自动递增重试）。
4. patch session 文件首行的 `cwd` 字段指向 worktree。
5. 在 worktree 中启动 pi，注入 `PI_CACHE_CWD` 环境变量。

> 并发 fork 时多个引导脚本可能算出相同编号，会逐个递增重试，直到 `git worktree add -b` 成功。

### `/tmux-fork-gt-clean`

列出并清理 `/tmux-fork-gt` 创建的 gittree worktree。支持三种用法：

```
/tmux-fork-gt-clean             # 弹窗选择要删除的 worktree
/tmux-fork-gt-clean all         # 清理全部空闲 worktree（跳过 in-use）
/tmux-fork-gt-clean <name>      # 直接清理指定名称，跳过弹窗
```

| 参数 | 说明 |
|------|------|
| `all` | 批量清理所有空闲 worktree，被进程占用的会跳过 |
| `<name>` | worktree 名称（`gittree-<name>-task` 中的 `name` 部分），不区分大小写 |

**安全机制：**

- 通过 `lsof` 检测进程占用（cwd 在该 worktree 下的 `pi` / `node` / `bash` 进程），标记为 `in use`。
- `in use` 的 worktree 会被拦截，不会删除。
- 删除前会二次确认（弹窗或 `ctx.ui.confirm`）。
- 清理后自动执行 `git worktree prune`。

> **子 agent 限制：** `/tmux-fork-gt` 与 `/tmux-fork-gt-clean` 在 fork-gt 出来的子 agent（worktree 会话）里**不注册**，命令列表中不可见。子 agent 再开 worktree 会嵌套混乱，且 clean 按 git root 扫描所有 `.worktrees/gittree-*`，可能误删兄弟或父会话的 worktree。`/tmux-fork`（普通 fork，不开 worktree）不受影响，仍可在子 agent 中使用。

## 配置

在 `~/.pi/agent/settings.json` 中添加 `tmuxFork` 字段：

```json
{
  "tmuxFork": {
    "mode": "new-window",
    "closeOnExit": false
  }
}
```

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `mode` | `"new-window"` \| `"split-window"` | `"new-window"` | 打开方式。`new-window` 整页新窗口；`split-window` 在当前窗口分屏（仅一个 pane 时右侧水平分屏，多个 pane 时在最后一个 pane 下方垂直分屏） |
| `closeOnExit` | `boolean` | `false` | pi 退出后是否关闭 tmux 窗口。`true` 直接关闭；`false` 保留 shell，方便查看输出 |

未配置时使用默认值。

## 工作原理

### 缓存复用

fork 时调用 `SessionManager.forkFrom(src, cwd)`，创建一个新的 session 文件，复制父会话全部非 header 条目，并写入新 header（`parentSession` 指向源文件）。由于发送给 LLM 的 prompt 前缀（system prompt + 历史轮次）字节级一致，隐式缓存模型能直接命中父会话已建立的缓存。

### worktree 场景下的缓存修复

`/tmux-fork-gt` 会把子 agent 的 cwd 切换到 worktree 路径。pi 的 system prompt 末尾会写入 `Current working directory: <cwd>`，而隐式缓存按 prompt 前缀逐 token 匹配——cwd 变化会导致末尾这行及其后的全部对话历史缓存未命中。

解决方式由 `cwd-cache-fixer.ts` 扩展完成：在 `before_agent_start` 事件中，把 system prompt 中的 cwd 字符串替换回原仓库根（由 `gittree-bootstrap.mjs` 通过 `PI_CACHE_CWD` 环境变量注入）。仅影响发送给模型的字符串，bash 与文件工具的真实执行 cwd 仍是 worktree，隔离性完全保留。

> 未设置 `PI_CACHE_CWD` 时，`cwd-cache-fixer` 不注册任何处理逻辑，零开销。

## 文件说明

| 文件 | 说明 |
|------|------|
| `index.ts` | 扩展入口，注册三个 `/tmux-fork*` 命令 |
| `gittree-bootstrap.mjs` | gittree worktree 创建与 pi 启动引导脚本，由 `/tmux-fork-gt` 调用 |
| `cwd-cache-fixer.ts` | worktree 场景下修复 prompt 缓存命中率的扩展，由 bootstrap 注入 |

## 常见问题

### 提示 `Not inside tmux.`

当前不在 tmux 会话内。请先 `tmux` 或 `tmux a` 进入会话再执行命令。

### 提示 `No session file (ephemeral).`

当前会话是临时会话，没有持久化的 session 文件，无法 fork。请使用持久化会话。

### `/tmux-fork-gt` 提示 `Usage: /tmux-fork-gt <brief task description>`

缺少任务描述参数。该命令需要一句话描述要执行的任务，例如：

```
/tmux-fork-gt 修复用户列表分页 bug
```

### `/tmux-fork-gt` 报 `git rev-parse --show-toplevel` 失败

当前目录不在 git 仓库内。该命令要求 cwd 位于某个 git 仓库中。

### 清理时提示 `is in use, cannot remove.`

该 worktree 下有进程正在运行（`pi` / `node` / `bash`）。请先关闭对应进程后再清理，或直接删除其他空闲的 worktree。

这是预期行为。pi 退出后会保留 shell，方便查看输出。若希望 pi 退出即关闭窗口，把 `closeOnExit` 设为 `true`。

### fork-gt 出的子 agent 里找不到 `/tmux-fork-gt`

这是预期行为。worktree 子 agent 会话中 `/tmux-fork-gt` 与 `/tmux-fork-gt-clean` 被禁用（不注册），避免嵌套 fork 造成混乱或误删兄弟 worktree。子 agent 里仍可用 `/tmux-fork`（普通 fork）。
