[English](./keybindings.md) | **中文**

# 快捷键与配置

默认键位的唯一真实来源是 `src/config/keymap.ts`。可在 `~/.pi/agent/lazy-panel.json` 的 `keymap.<scope>.<action-id>` 下覆盖任意键位（scope：`global`、`sessions`、`tree`、`content`、`tree-dialog`）。动作 id 就是 `src/types.ts` 里的 `ActionId` 联合类型。

## 配置文件

配置文件位于 `~/.pi/agent/lazy-panel.json`，所有字段均为可选，缺省时使用内置默认值：

```json
{
  "locale": "zh",
  "defaultScope": "all",
  "defaultSort": "recent",
  "leftColumnRatio": 0.25,
  "keymap": {
    "global":   { "help": "F1", "scope-all": ["A", "ctrl+space"] },
    "sessions": { "session-delete": "ctrl+d", "session-share": null }
  }
}
```

| 字段 | 说明 | 可选值 / 范围 | 默认 |
| --- | --- | --- | --- |
| `locale` | UI 语言 | `"en"` / `"zh"` | 跟随系统语言 |
| `defaultScope` | 打开时的会话范围 | `"current-folder"` / `"all"` | `"current-folder"` |
| `defaultSort` | 会话列表排序 | `"recent"` / `"created"` / `"title"` / `"threaded"` | `"recent"` |
| `leftColumnRatio` | 左侧列宽占比 | `0.15` ~ `0.6` | `0.25` |
| `keymap` | 自定义快捷键（见下） | 按 scope 分组 | 内置键位 |

- 非法的 `locale` / `defaultScope` / `defaultSort` 会退回默认值，越界的 `leftColumnRatio` 也会被忽略，并在面板打开时于 footer 提示。
- `locale` 缺省时按系统语言自动检测（见 `src/i18n/index.ts`）。

## 插件设置

在三个主面板按 **`,`（逗号）**，在 `?` 中选择「打开插件设置」，或直接运行 **`/lazy-panel settings`**。即使 `settings-open` 被解绑，命令入口仍可用。已有弹窗和搜索输入继续独占按键，完整树对话框内不打开设置。

**常规**支持编辑界面语言、默认会话范围和默认排序（不是临时浏览选择）。Enter 打开选择菜单，选中那一刻就写入配置并生效，不再有单独的保存步骤。语言立即生效；默认范围和排序下次打开面板才使用，不切走或重排当前会话列表。「跟随系统」删除语言 override。

**快捷键**支持编辑：顶部**作用域**行选择要编辑的作用域（全局 / 会话列表 / 会话树 / 会话内容 / 树对话框），下面每个动作一行显示当前绑定。在动作行按 Enter 打开管理菜单：`添加按键…`（按键录入，**直接按下想绑的键**，字段里实时显示，按 `Enter` 添加；`Backspace` 退回上一个键，所以多键序列按 `g` `g` 这样录；`Esc` 取消）、`删除：<键>`（删掉某个已绑的键）、`解绑（无按键）`、`恢复默认`。每次编辑都立刻写入并生效（重建键位，未完成的多键序列会被丢弃）；写成功后菜单重新打开，方便接着编下一条，写失败就留在主视图看错误和重试。`d`（恢复默认）在快捷键页会把当前作用域的所有动作恢复为内置默认。布局宽度也可编辑。

**按键冲突会被拦下来**：同一个作用域里先精确匹配上的那条拿走按键（精确匹配永远赢过还在等下一键的长序列，所以短键不管排第几都赢），因此后一个同键绑定是**按不出来**的。添加（或 `恢复默认`）时如果新键被同作用域里另一个动作占着，会弹一个处理菜单：`从 <动作> 手里抢过来`（把这条键从占用者手里摘掉，摘空了就是解绑）或 `取消这次改动`（草稿还原到编辑前那份，包括之前没落盘的改动）。按键本身还按得出来、但会让另一个动作的同键/前缀序列不再触发的（`d` 被排在后面的动作抢走、`g` 让 `gg` 失效），照常写入，落盘后在设置框里补一句提示。配置文件里**本来就有的**重复键不会拦住一次无关的编辑（比如删除自己另一条键）。

**主题**只有一行，显示 pi 当前的主题；Enter 打开菜单，列出 pi 能加载的全部主题。选中就立刻切到 pi——面板和 pi 的界面一起换色，并由 pi 把它写进自己的设置。pi 拒绝这个主题时，失败的那个留在行上（`*` 标记 + 错误提示），按 `s` 重试；按 `d` 放弃这次切换。

| 设置内的固定键 | 操作 |
| --- | --- |
| `h` / `←` | 聚焦左侧分类，已在左侧时保持不动 |
| `l` / `→` | 聚焦右侧配置列表，已在右侧时保持不动 |
| `Tab` / `Shift+Tab` | 循环切换分类、配置列表和操作按钮 |
| `j/k/↑/↓` | 选择分类、条目或按钮，保证选中项可见 |
| `Enter` | 从分类进入列表、编辑常规项、选择快捷键作用域、编辑某个动作的绑定、打开主题菜单或执行按钮 |
| `s` | 重试上一次没成功的写入（保存失败，或 pi 拒绝的主题切换）；只在有失败待处理时才作为「重试」按钮出现，无需先 Tab 聚焦按钮 |
| `d` | 确认后恢复默认设置（常规 / 布局 / 当前快捷键作用域），或放弃切换失败的那个主题，无需先 Tab 聚焦按钮 |
| `r` | 重新读取配置，有没保存成功的改动时先确认放弃 |
| 配置列表内 `PageUp` / `PageDown` | 滚动较长的条目说明（底部说明框只在**带说明**的设置项上出现：布局宽度、快捷键各行，以及切换失败 / 一个主题都没有时的主题行；框里只写说明，条目的名字和值看上面的列表行） |
| `Esc` / `q` | 退出设置；有没保存成功的改动时选择继续编辑、重试并退出或确认放弃 |

底部提供关闭、恢复默认、重新读取按钮，有失败待处理时还会多出重试按钮；恢复默认先确认，确认即写入。子选择菜单优先消费按键，Esc 只取消本次选择。浏览其他快捷键作用域时，选中**作用域**行按 Enter。

**Esc 和 Backspace 任何作用域都绑不了**：录入器不接受，写进 `lazy-panel.json` 也会被丢掉并在 footer 提示。Esc 被面板自己占用（清搜索、清多选、退出面板，在键位表之前就吃掉了，绑了也不会触发）；Backspace 是录入器的“退回上一个键”，也是所有输入框编辑要用的键。**单独的修饰键**（Ctrl / Shift / Alt / Fn）同样不能绑——终端不会把它当按键上报，chord 必须有基础键（如 `ctrl+d`）。**Fn 比它们更彻底：当修饰键也不可能有。** 它在终端之下就被处理掉了（键盘固件 / 系统驱动），传上来的只是结果——多数笔记本上 `Fn`+`F1` 发的是“音量减”而不是 `F1`，kitty 键盘协议的修饰位里也根本没有 `fn`。觉得某个键按不出来时，先试试打开 Fn-lock 或换个修饰键。其余都支持，包括功能键和 Delete / Home / End / PageUp / PageDown；在 kitty 键盘协议的终端（如 herdr）里同样能录、能触发。带修饰键时是另一个键：`ctrl+backspace` 可以绑。

弹窗即时打开/关闭，不再播放动画。以上管理键固定，不依赖自定义 keymap，窄终端也保持相同的逻辑焦点含义。设置打开期间背景仍然屏蔽鼠标操作，设置页自己开始响应鼠标（点分类 / 点设置行 / 点按钮）。快捷键行**只展示所选作用域本层的绑定**，「本层无绑定」不代表其他作用域没有绑定，实际执行仍受继承、优先级与禁用动作影响；说明框里写着当前是哪个作用域。

保存仅修改 `lazy-panel.json` 中触及的字段（含 `keymap` 下按作用域 / 动作粒度的改动），保留其他配置及未知字段。原文件的排版也照旧保留——缩进、CRLF / LF 行尾、末尾换行（写成一行的配置仍然保持一行），所以手工维护或放在 dotfiles 仓库里的那份不会因为改一个值就变成整文件 diff；面板自己新建的文件是两空格缩进带末尾换行。坏文件不覆盖，修正后可重新读取。其他窗口的不相交修改自动合并，同字段 / 同一条绑定冲突时停止写入——改动留在面板上并显示错误和重试按钮（`s`），修好或重新读取后再重试。仅打开或关闭设置不会写文件——每一处改动都是改的时候立刻写入。

## 自定义快捷键

```json
{
  "keymap": {
    "global":   { "help": "F1", "scope-all": ["A", "ctrl+space"], "focus-sessions": "F5" },
    "sessions": { "session-delete": "ctrl+d", "session-share": null }
  }
}
```

- 一个值是**一个 chord** 或 **chord 数组**。它**替换**该动作的默认键位（不是追加），`null` 表示解绑该动作。
- **配置名称就是动作 id**（下面每张表格中间那一列），把它放到对应 scope 下即可，例如给 SESSIONS 的删除换成 `ctrl+d`：`{ "keymap": { "sessions": { "session-delete": "ctrl+d" } } }`。每个面板小节的 scope 见其标题：全局→`global`、SESSIONS→`sessions`、TREE→`tree`、CONTENT→`content`、树对话框→`tree-dialog`（表格里标注了非本 scope 的例外）。

- Chord 语法（`src/config/keys.ts`）：
  - 单键：`j`、`?`、`/`、`1`、`tab`、`enter`、`escape`、`space`、`up`、`pageDown`、`f1`
  - 修饰键：`ctrl+d`、`shift+tab`、`alt+x`、`ctrl+shift+p`（顺序任意）
  - 大写字母是 shift 的简写：`G` = `shift+g`
  - 多键序列：`gg`、`yy`（原样连写），或空格分隔的 `ctrl+w h`
- 面板键位会遮蔽同一个键的全局键位（例如 `n` 在 SESSIONS 面板是「新建会话」，在其它面板是「下一个匹配」）。`h` / `l` 没有被任何默认面板键位遮蔽，所以切换面板在哪里都一样。
- 树对话框按 `tree-dialog` → `tree` → `global` 顺序解析：它自己的键（`d` `t` `u` `l` `a` `q`）遮蔽面板与全局键位（`a` 变成过滤而不是打开对话框，`l` 变成「只看有标签」过滤而不是「下一个面板」，`q` 变成关闭对话框而不是退出），其余都沿用树面板的键位。
- 无效的 chord 或未知 scope 会被跳过，并在面板打开时显示在 footer。
- 多键序列最多等待下一个键 1 秒；`Esc` 丢弃打了一半的序列。
- 面板标题显示绑定到 `focus-<pane>` 的跳转键（`[1] SESSIONS`），所以重绑它标题也会跟着变。

## 全局快捷键

`j` / `↓`（`move-down`）和 `k` / `↑`（`move-up`）是全局绑定，三个面板和树对话框共用。修改或解绑默认值请配置 `keymap.global`；在面板里设为 `null` 只移除该面板的覆盖，不会屏蔽继承的全局绑定。

帮助弹窗固定 28 行（含边框），小终端自动缩小。通过 `j/k/↑/↓` 选择命令并上下滚动。底边按需显示「↑ 上方还有更多」「↓ 下方还有更多」，到达对应边界后提示消失。

| 键 | 配置名称 | 操作 |
| --- | --- | --- |
| `l` / `Tab` | `focus-next` | 聚焦下一个面板 |
| `h` | `focus-prev` | 聚焦上一个面板 |
| `1` `2` `3` | `focus-sessions` / `focus-tree` / `focus-content` | 直接聚焦 SESSIONS / TREE / CONTENT |
| `C` | `scope-current` | 列表范围：当前文件夹（Current folder） |
| `A` | `scope-all` | 列表范围：全部（All） |
| `?` | `help` | 当前面板的命令列表：`j` / `k` / `↑` / `↓` 选择，`Enter` 关闭帮助并执行选中命令；`?` / `Esc` / `q` 只关闭，不执行 |
| `,` | `settings-open` | 即时打开插件设置，可编辑并保存常规、布局、快捷键和主题，详见上方「插件设置」 |
| `q` / `Ctrl+c` | `quit` | 退出面板 |
| `@` | `changelog` | pi 的 changelog（`/changelog`）大弹窗：`j` `k` `↑` `↓` 滚动，`Ctrl+d` / `Ctrl+u` 半页，`g` / `G` 顶部 / 底部，`Esc` / `q` / `@` 关闭。整份文件渲染较慢，弹窗先在中间显示旋转方块 `◰ Loading changelog…`，随后显示内容（有缓存，第二次按 `@` 瞬间打开）。最新版本在最上面（pi 自己的 `/changelog` 放在最后）。树对话框里不可用 |
| `Esc` | —（内置，不可配置） | 依次：丢弃未完成的按键 → 清除当前面板的搜索 → 清除 SESSIONS 的多选 → 从归档返回普通列表 → 退出 |
| `/` | `search` | 搜索当前面板（见下）：底部搜索栏边输入边搜，`Enter` 保留关键字，`Esc` 取消 |
| `n` / `N` | `search-next` / `search-prev` | 当前面板搜索的下一个 / 上一个匹配，循环回绕。搜索生效时它们优先于同名的面板键位（`n` 只有在 SESSIONS 且没有搜索时才是「新建会话」） |

`C` 和 `A` 是单向的：已经在 *All* 时再按 `A` 无效。

`?` 帮助弹窗列出全部命令，按使用频率排序（最常用的在前，如 `Enter` 排在 vim 移动键之前）。每个可执行动作独立为一个可选项，同一动作的多个键位仍合并显示。「帮助」本身仅保留为灰色提示，移动时跳过，不能通过回车执行；直接按配置的帮助键仍可关闭弹窗。移动时跳过分区标题和描述续行，到边界停住、不回绕，并自动滚动使选中项可见；每次打开从首项开始。Enter 在当前面板执行所选动作，自定义/多键绑定也适用；删除、分叉、克隆等仍走原有确认流程。自定义的单键帮助绑定同样可关闭弹窗。底部 footer 只提示每个面板的少数几个键；长尾（排序 / 信息 / 压缩 / 分叉 / 克隆 / 导出 / 导入 / 分享 / changelog）只在 `?` 里能看到。

## 搜索（`/`）

lazygit 风格，每个面板各记各的：列表行永不过滤，光标在匹配之间跳转。`/` 在底部打开当前面板的 `搜索:` 栏；每敲一个键都跳到「光标位置或其之后的第一个匹配」（到底则回绕到第一个），没有匹配时把光标放回原位。`Enter` 保留关键字并把按键交回面板，搜索栏里的 `Esc` 丢弃关键字并恢复光标（在 TREE 里还恢复折叠）。之后面板标题显示 `2/7 matches`（光标不在匹配上时显示 `7 matches`，无匹配显示 `no matches`），footer 显示关键字和 `n next  N prev  Esc clear`，`n` / `N` 在匹配间步进、两端回绕。面板里的 `Esc` 结束该面板的搜索。每个面板保留自己的关键字，所以切换面板不会丢失；只有当前聚焦面板的搜索会被 `n` / `N` 步进，其它面板的命中仍保持高亮。命中文字会高亮；当前匹配（光标所在的那个）反色，以便和其它命中区分。

关键字大小写不敏感、且每个词都必须出现。裸词（不带限定词）只匹配「命名一行的内容」（会话名 / 预览、树节点 label / 正文、内容正文）；模型、路径、角色、日期都需要用 `key:value` 限定词（同类限定词以最后一个为准，未知 key 和无法解析的日期当作裸词搜索，某个面板没有的字段对应的限定词被忽略）：

| 面板 | 裸词匹配的字段 | 限定词 |
| --- | --- | --- |
| SESSIONS | 会话名和首条消息预览（行的标题）；模型和工作目录不参与裸词匹配 | `name:`（仅会话名）、`model:`（仅模型）、`path:`（工作目录，完整路径或 `~/…` 写法）、`after:` / `before:`（最后更新，`YYYY-MM-DD`）；`tag:` 被忽略 |
| TREE | label 和正文；折叠的行也算。角色不参与裸词匹配（与 `/tree` 不同） | `tag:`（仅 label）、`role:`（`user` / `assistant` / `system` / `tool`）、`after:` / `before:`（节点时间）；`name:` `model:` `path:` 被忽略 |
| CONTENT | 消息渲染后的正文行（跳过消息框头部）；命中会被滚到面板顶部 | 无（限定词被丢弃，只搜裸词） |

跳到折叠分支里的 TREE 匹配会展开它，右侧 CONTENT 照常跟随光标。树对话框（`a`）有自己的搜索行，它是**过滤**而不是跳转，两者互不影响。

## SESSIONS 面板

面板打开时光标落在 pi 当前打开的会话上（因此 TREE / CONTENT 显示当前对话）；一个尚未列出的全新会话则把光标停在第一行。`C` / `A` 切换范围后仍从顶部开始。`/` 搜索会话名和首条消息预览，`model:` / `path:` 触及模型和工作目录（见上文*搜索*）；该搜索生效期间 `n` / `N` 步进匹配，而不是 `n` 新建会话。

| 键 | 配置名称 | 操作 |
| --- | --- | --- |
| `j` `k` `↑` `↓` | `move-down` / `move-up`（global） | 移动光标 |
| `gg` / `G` | `go-top` / `go-bottom` | 顶部 / 底部 |
| `J` / `K` | `scroll-content-down` / `scroll-content-up` | 滚动右侧 CONTENT 面板 |
| `Enter` | `session-resume` | 恢复会话：pi 切换过去、面板关闭；失败（文件缺失、切换取消）停留在 footer |
| `d` | `session-delete` | 删除：居中的 *Delete session?* 框问 Yes / No（光标停在 No，`y` / `n` 直接选，`Enter` 确认高亮项，`Esc` 取消）。和 pi 的 `/resume` 一样，有 `trash` CLI 时移入系统回收站，否则直接 unlink（footer 说明用了哪种）；pi 当前打开的会话会被拒绝、不询问。列表重新加载，光标夹回范围内，TREE / CONTENT 跟随 |
| `r` | `session-rename` | 在预填当前名字的居中框里改名（`Enter` 保存，`Esc` 取消，空值清除名字；等同 `/name` 或 `/resume` 里的 ctrl+r）。列表重新加载，光标仍停在该会话 |
| `n` | `session-new` | 新建会话（`/new`）：居中的 *New session* 框询问名字（`Enter` 创建，`Esc` 取消，**留空则以未命名启动** —— `/name` 的 `[name]` 参数不设置）。pi 切换到新会话、面板关闭 |
| `o` | `session-fork` | 分叉（`/fork`）：居中选择器列出会话的用户消息（光标停在最后一条，和 pi 自带 `/fork` 一致；`j` / `k` 移动，长列表可滚动），`Enter` 选中后 *Fork session?* Yes / No 框确认（`Esc` / No 退回选择器）。分叉从那条消息**之前**开始，pi 把它的文本填回编辑器。非当前会话会先切过去；没有用户消息时提示 *No messages to fork from* |
| `y` | `session-clone` | 克隆（`/clone`）：*Clone session?* Yes / No 框确认（光标停在 No），随后把活动分支复制到新会话文件、pi 打开它 |
| `Y` | `session-copy-last-reply` | 复制最后一条 assistant 回复到剪贴板（`/copy`）：只取其文本片段，跳过思考和工具调用；面板保持打开，footer 报告结果（*copied last reply* 或 *no assistant reply to copy*） |
| `Space` | `session-toggle-select` | 切换多选：不画标记字形 —— 选中行的标题着色（accent），在长列表里凸显 —— 标题以 `3 selected` 开头。有选中时 `d` 在一次 *Delete N sessions?* 确认后删除全部（跳过 pi 打开的会话；失败的仍列出并保持选中，第一条错误进 footer），而 `r` `o` `y` `e` `S` 拒绝（*cannot act on multiple sessions*）。`Esc` 先清空选中、再按才退出 |
| `p` | `session-pin` | 置顶 / 取消置顶光标所在会话（有多选时作用于所有选中会话）：只要有未置顶的就整组置顶，否则整组取消置顶。置顶的会话归到列表最上一个 dim 的 `── PINNED ──` 分隔线下（其余在 `── OTHERS ──` 下）、不受排序影响 —— 最后置顶的排在更早置顶的上面；排序和搜索仍然作用于它们，但 `C` / `A` 范围过滤可能挡住其它目录里的置顶会话（其置顶记录会保留）。置顶与归档共同持久化在 `~/.pi/agent/lazy-panel-state.json`；归档会话必须先取消归档再置顶。详见下方「归档」 |
| `e` | `session-export` | 导出（`/export`）：居中 *Export as* 菜单选 **HTML**（整棵树，由 pi 自己的 `pi --export` 渲染）或 **JSONL**（活动分支，可重新导入），然后 *Export to* 框预填 pi 的默认路径（pi 工作目录下的 `pi-session-<file>.html` / `session-<time>.jsonl`）。相对路径和 `~` 在所有平台通用；目录（已存在，或以 `/` 结尾输入）会在里面用默认文件名。框里 `Esc` 退回菜单；已存在的文件先问 *Overwrite file?*。任意会话都能导出，不只当前会话；面板保持打开，footer 说明文件去向 |
| `I` | `session-import` | 导入（`/import`）：居中框询问一个会话 `.jsonl`（相对 pi 工作目录，允许 `~`），*Import and switch to it?* Yes / No 框确认，随后文件被复制进当前会话目录（重名加 `-1` 后缀）、pi 切换过去、面板关闭。缺失 / 空 / 非 pi 文件会在 footer 报告，不复制任何东西 |
| `S` | `session-share` | 分享（`/share`）：三项目的地菜单 —— *Auto (Gist first)* / *GitHub Gist* / *Radius*。选中一项本身就是外发确认（Esc / q 不上传任何东西）；初始光标落在 pi 的 `sessionShareDestination`（没配置为 auto）。Auto 优先 Gist：`gh` 已登录就上传 Gist，否则回退 Radius；显式选 Gist / Radius 则绝不切换。显式选 Radius 但没登录时，面板接管 pi 的 `/login radius`：先选账号（OAuth）/ API Key，API Key 在写入 pi 之前先向网关校验（无效只在 footer 报错、不保存）；账号登录再用同一菜单选浏览器 / 设备码，浏览器登录自动开浏览器并把链接复制到剪贴板（不挡面板，Esc 取消等待），设备码显示在只读提示框里（`y` 复制、Esc 取消登录），登录成功自动继续分享。已登录时 Radius 菜单提供 *Share to Radius* / *Sign out of Radius*，退出会先确认再删除本机凭据（网页端会话不失效）。这些菜单的标题栏右侧显示 `Esc/q 关闭`，不再显示会话标题。Radius 把活动分支的 JSONL 发到 pi 的默认网关（返回 canonical 链接）；Gist 路径渲染 HTML 并用 `gh gist create --public=false`，需要已登录的 GitHub CLI。Gist 缺工具 / Auto 两边都不可用时按 pi 的原文提示。链接会复制到剪贴板并显示在 footer |
| `s` | `session-sort` | 循环排序：recent（最后更新）→ created（创建时间）→ title（按显示的标题 —— 有名字用名字，否则用首条消息预览 —— A–Z，空会话排最后）→ threaded（分叉缩进挂在父会话下）→ …；标题显示当前排序，光标跟随其会话 |
| `i` | `session-info` | 会话信息居中框（即 `/session` 展示的：名称、模型、消息数、token、费用、创建 / 更新、路径、id）；`y` 复制全部文本，`Esc` / `q` 关闭 |
| `u` | `session-context-usage` | 上下文占用居中框：模型的上下文窗口有多满（pi footer 里那个百分比），拆分方式借鉴 pi-cc 的 `/context`。一行 `已用 / 窗口 · 百分比`、一条彩色堆叠进度条，随后每个类别按占整个窗口的比例列一行——**系统提示词 / 记忆 / 技能 / 工具定义 / 工具结果 / 对话上下文 / 其他 / 空闲**——再加模型 / 消息数 / 费用概要。当前会话给出完整拆分（提示侧来自 pi 的运行态）；其他会话只能从文件里算出工具结果和对话上下文，真实请求里的其余部分归到「其他」（窗口按 model id 反查，查不到只显示 token）。`j` / `k` / `↑` / `↓` 选择分类，`Enter` 打开对应提示词的 Markdown 详情（跳过只有统计值的「其他 / 空闲」）。详情内 `j` / `k` / 方向键滚动，`d` / `u` / PageDown / PageUp 翻半页，`g` / `G` 到顶部 / 底部，`y` 复制提示词，`Esc` / `q` 返回占用列表并保留选中项。两层弹窗内部不再重复快捷键提示（统一显示在面板底部 footer），框内只放内容。占用列表内 `y` 复制概要，`Esc` / `q` 关闭；多选时拒绝 |
| `c` | `session-compact` | 压缩（`/compact`）：居中 *Compact* 框询问可选的聚焦指令（`Enter` 压缩，`Esc` 取消，留空用 pi 默认）。压缩光标会话的活动分支后 pi 打开它 —— 非当前会话会先切过去（这就是「进入对话」）。压缩期间 pi 自己的 footer 显示旋转 spinner；成功后面板关闭，失败（无模型、会话太小、已压缩）停留在 footer。选中多个会话时 `c` 拒绝 |

## 归档

| 键（SESSIONS scope） | 配置名称 | 操作 |
| --- | --- | --- |
| `x` | `session-archive` | 归档光标会话，有多选时归档所选会话；归档视图里改为取消归档。单个和批量均无需确认。归档同时取消置顶，取消归档不恢复原置顶 |
| `X` | `session-archive-view` | 在同一个面板切换普通 / 归档列表，保留 C/A 目录范围和排序 |

- 归档只是**本插件的整理状态**，不是删除、备份或上下文压缩。不会移动或改写 JSONL，会话内部整棵树保留；pi 自带 `/resume` 仍会列出。独立的 fork / clone / 导入副本不继承归档状态（按文件路径识别，不按会话 UUID）。
- 两种视图都能浏览 TREE / CONTENT，复制、查看信息、导出等操作不变。`Enter`（包括 TREE 恢复和双击）只进入会话，**不会取消归档**。`d` 仍需确认，仍拒绝删除当前会话。
- 允许归档当前正在使用的会话，不中断对话。重新打开面板默认仍是普通列表；若当前会话已归档，footer 会提示如何查看。
- C 只看当前目录，A 看所有目录，两种视图中含义一致。搜索只匹配当前列表。本次面板打开期间，两种视图各自记住搜索词、光标对应的**文件**和滚动位置，匹配行号重新计算；切视图或目录范围清空多选，返回时目录范围已变化则从顶部开始。
- 归档 / 取消归档后，光标保留在幸存会话上，或选下一条，末尾则选上一条；列表为空时清空 TREE / CONTENT。Esc 先处理半截按键、搜索、多选，再从归档返回普通列表；`q` 直接退出。归档视图 footer 按自定义键位显示返回和取消归档提示。
- 状态文件为 pi agent 目录下的 `lazy-panel-state.json`，置顶和归档一次原子写入。仅当新文件不存在时读取旧 `lazy-panel-pins.json`，首次成功操作时迁移，旧文件保留不动。升级后请重启或 reload **所有** pi 窗口，避免旧窗口继续使用旧存储。状态损坏 / 不可读时明确报错，不用空集合覆盖；会话路径暂时不可访问时不会自动删除记录。本面板确认删除成功后清理两种状态，清理失败单独提示。
- 多个 pi 窗口通过跨进程锁串行写入，锁内读取最新状态并合并本次操作，不覆盖彼此的旧快照。若进程崩溃留下 `lazy-panel-state.json.lock`，等待五秒后会报错并给出锁路径；先关闭**所有 pi 进程**，只移除该遗留锁目录，再重试。仍可能有写入进程时不要删锁。原子替换避免半份 JSON，断电持久性仍由操作系统决定。

## TREE 面板

面板把树显示为折叠大纲：`▸` 折叠的旁支，`▾` 展开的旁支，`─` 从未继续的旁支；分支内的行每层缩进两列（最多四层，更深以 `… ` 代替）。旁支默认折叠，活动分支展开。过滤住在树对话框（`a`）里，对话框用 pi 风格的引导线画同样的行，并有自己的实时搜索行来收窄列表。面板里的 `/` 是上文*搜索*的跳转式搜索（label / 正文，`tag:` 匹配 label，`role:` 匹配角色；折叠分支里的匹配会被展开）。在对话框里选的过滤会保留：面板列出同样过滤后的树，标题也标出（`2/12 · user-only`）；面板打开时采用 pi 自己的 `treeFilterMode` 设置（`/tree` 启动时的过滤）。

| 键 | 配置名称 | 操作 |
| --- | --- | --- |
| `j` `k` `↑` `↓` | `move-down` / `move-up`（global） | 移动光标 |
| `gg` / `G` | `go-top` / `go-bottom` | 顶部 / 底部 |
| `Enter` | `tree-restore` | 恢复到节点，和 `/tree` 一样：居中菜单问 *No summary / Summarize / Summarize with custom prompt*（`j`/`k`/`↑`/`↓` 移动，`Enter` 选，`Esc` 退回树）；自定义提示是单行输入（`Enter` 摘要，`Esc` 退回菜单）。需要时先切换到该会话。节点本身就是叶子（`Enter` 直接关闭面板）或 pi 的 `branchSummary.skipPrompt` 开启时不弹菜单 |
| `z` | `tree-fold` | 折叠 / 展开光标所在分支：在 `▸` / `▾` 行上切换，在分支内任意行则折叠该分支并跳到段头（vim 的 `zc`）；线性对话的主干没有可折叠的段 |
| `y` | `tree-copy` | 复制节点文本（完整文本，等同 `/tree` 里的 `Ctrl+x`） |
| `L` | `tree-label` | 在居中对话框里添加 / 编辑 label，类似 lazygit 的 commit 弹窗（`Enter` 保存，`Esc` 取消，空值删除；等同当前 pi `/tree` 里的 `Shift+L`） |
| `T` | `tree-toggle-label-time` | 显示 / 隐藏标签最后修改时间，放在 `[标签 · MM-DD HH:mm]` 内，与消息时间区分。和完整树对话框共用，默认隐藏，切换会话保留，重开面板重置。时间缺失或无效则省略；只改显示，不修改会话和搜索匹配 |
| `f` | `tree-filter-menu` | 打开五行过滤菜单：`d` 默认 / `t` 无工具结果 / `u` 仅用户 / `l` 仅标签 / `a` 全部。默认选中当前过滤；`j` / `k` / 方向键移动，`Enter` 选择，`Esc` 取消，也可用五个字母直接选择。重复选择当前模式保持不变，不做 toggle |
| `a` | `tree-open` | 打开树对话框：更大的树视图（顶部搜索行，中间是树行），和面板共享过滤及折叠状态；键位见下，快捷键显示在底部 footer |

过滤双向同步：小面板在 `f` 菜单选 `u` 后，打开大对话框仍是仅用户模式；在大对话框换过滤，返回小面板也沿用。`a` 表示放大视图，不代表取消过滤。切换过滤会清空折叠，光标保留在原节点或最近的可见祖先，右侧 CONTENT 同步；两边的搜索关键字仍各自独立。

## 树对话框（在 TREE 面板按 `a`）

整棵树的大弹窗：顶部搜索行，中间是行（pi 风格引导线，折叠的行在连接处显示 `⊞`）。快捷键只在面板底部 footer 显示（框内不重复，所以对话框里没有 `?` 帮助）。对话框有自己的光标；关闭时面板光标移到它上面。按键先用 `tree-dialog` scope，再树面板的键位，最后全局的，所以 `j` `k`、`gg` `G`、`Enter`、`y`、`L`、`T`、`z` 和 `/` 是面板的键（并跟随它们的重绑），而对话框自己的键在 `keymap."tree-dialog"` 下。切换面板（`h` `Tab` `1`..`3`）、列表范围（`C` `A`）、`n` `N`、`?`、`@` 和退出面板（`Ctrl+c`）在这里都无效。

下表「配置名称」后括号标出它属于哪个 scope（要改这些键就在对应 scope 下改）：过滤键和关闭键在 `tree-dialog`，移动 / 恢复 / 复制 / 标签 / 折叠沿用 `tree`，搜索沿用 `global`。

| 键 | 配置名称（scope） | 操作 |
| --- | --- | --- |
| `j` `k` `↑` `↓` | `move-down` / `move-up`（global） | 移动对话框的光标 |
| `gg` / `G` | `go-top` / `go-bottom`（tree） | 顶部 / 底部 |
| `/` | `search`（global） | 聚焦搜索行；输入实时过滤行，和 `/tree` 一样：每个词都要出现在行的 label / 正文里（大小写不敏感），`tag:x` 收窄到 label，`role:user` 到角色，`after:2026-09-01` / `before:2026-09-20` 到日期。`Esc` 把按键交回列表并保留关键字（行保持收窄）；再按 `/` 编辑它，删光文本即清除。搜索行里 `Enter` 无含义。关键字生效时全部匹配都显示（折叠被清除，和 `/tree` 一样）；关键字清空或关闭对话框后折叠恢复 |
| `Enter` | `tree-restore`（tree） | 恢复到该行，和面板完全一样（含摘要菜单） |
| `y` | `tree-copy`（tree） | 复制该行的文本 |
| `L` | `tree-label`（tree） | 添加 / 编辑该行的 label（Label 对话框叠在树对话框上面） |
| `T` | `tree-toggle-label-time`（tree） | 显示 / 隐藏标签时间，与 TREE 面板同步；搜索框或标签输入框聚焦时按键由输入框处理，不切换显示 |
| `z` | `tree-fold`（tree） | 折叠 / 展开光标所在分支（规则和折叠状态与面板相同） |
| `d` `t` `u` `l` `a` | `tree-filter-default` / `tree-filter-no-tools` / `tree-filter-user` / `tree-filter-labeled` / `tree-filter-all`（tree-dialog） | 过滤：default（隐藏记账信息）/ 无工具结果 / 只看 user / 只看有 label / 全部；`t` `u` `l` `a` 再按一次回到 default（对应 pi 的 `Ctrl+d/t/u/l/a`）。树被重新加载、折叠被清除，面板随后显示同样的过滤 |
| `Esc` / `q` | `tree-dialog-close`（tree-dialog；`Esc` 内置不可配置） | 关闭（在列表上）：面板光标落到对话框的行上（展开挡住它的折叠），CONTENT 跟随。在搜索行里 `Esc` 只离开搜索行，`q` 只是一个字母 |

## CONTENT 面板（只读）

滚动、搜索，另加把选中的消息（TREE 光标高亮、带 `›` 标记的那条）居中和复制。

| 键 | 配置名称 | 操作 |
| --- | --- | --- |
| `j` `k` `↑` `↓` | `move-down` / `move-up`（global） | 滚动 |
| `gg` / `G` | `go-top` / `go-bottom` | 顶部 / 底部 |
| `zz` | `content-center` | 把选中的消息滚到面板中间（vim 的 `zz`）；内容不足以滚动时为空操作 |
| `y` | `content-copy` | 复制选中消息的全文到剪贴板（等同 TREE 面板的 `y`） |
| `/` | `search`（global） | 搜索渲染后的消息正文（只搜裸词，无限定词）；匹配行被滚到顶部，`n` / `N` 步进命中 |

## 鼠标

核心仍是键盘，鼠标只做轻量适配。regular（默认）和 fullscreen 两种 TUI 模式都支持：fullscreen 由 pi 把事件派给面板，regular 模式插件自己打开 SGR 鼠标上报（需要终端支持 SGR，现代终端基本都行；面板打开期间终端自己的选中 / 滚动被接管，关闭后恢复）。

| 操作 | 行为 |
| --- | --- |
| 滚轮 / 三指上下 | 指针在面板上：滚动该面板的视图（列表只滚动、不移动选中项；CONTENT 按行滚动），不改变焦点。指针在弹窗上：移动选中项（确认框 / 恢复菜单 / fork 选择器 / 帮助 / 树对话框 / 设置 / 上下文占用），或滚动正文（changelog、prompt 预览） |
| 单击 | 指针在面板上：焦点切到该面板，点在列表项上时同时把光标移到该项。在弹窗里：选中指针下的那一格（分类栏的分类 / 一条设置 / 一个按钮 / 树与列表的一行 / 一个上下文分类） |
| 双击 | 等于对该行按 `Enter`：进入会话、折叠 TREE 分支、选中菜单项、执行帮助里的命令、恢复树节点、编辑设置、运行设置里的按钮、打开上下文分类的预览 |
| 点在弹窗外面 / 边框上 | 什么都不做，弹窗不会因为点空白而关闭 |
| `press` / `move` / `drag` | 一律交回终端，终端自己的文本选择照常可用 |



