pi-open-tui 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,245 @@
1
+ # Configuration reference / 配置参考
2
+
3
+ [English](#english) | [简体中文](#简体中文) | [Default JSON / 默认 JSON](#default-json)
4
+
5
+ ## English
6
+
7
+ For installation and feature examples, see the [README](../README.md). This reference covers all settings accepted by `open-tui.json`. The sources are [config.ts](../extensions/open-tui/config.ts), [settings-command.ts](../extensions/open-tui/settings-command.ts), and [icons.ts](../extensions/open-tui/icons.ts).
8
+
9
+ ### Storage and loading
10
+
11
+ The file is stored in Pi's agent directory: `~/.pi/agent/open-tui.json` by default, or `<PI_CODING_AGENT_DIR>/open-tui.json` when that environment variable is set. The extension creates the file with defaults if it is missing and the directory is writable.
12
+
13
+ You only need to specify values you want to change; missing fields inherit [the defaults](#default-json). Flags must be JSON booleans, not strings such as `"false"`. Invalid known values fall back to their defaults, and unreadable or malformed JSON falls back to the default configuration with a warning. Direct file edits are loaded on `/reload` or a new session. If saving from `/open-tui` fails, the new values still apply to the current session and a warning asks you to check directory permissions and free space.
14
+
15
+ ### Settings dialog
16
+
17
+ | Tab | Sections |
18
+ | --- | --- |
19
+ | General | Extension, Interface, Messages & tools, Footer |
20
+ | Appearance | Icons, Editor |
21
+ | Footer | Location, Git, Runtime, Usage, Other |
22
+ | Telemetry | Enabled (standalone master switch), Measurements |
23
+
24
+ Tab/Shift+Tab or ←/→ switches tabs; ↑/↓ or Page Up/Page Down moves between settings; Enter/Space changes a value; Esc/q closes the dialog. Dim section headings occupy rows but cannot be selected or changed. The scroll position counts both headings and settings.
25
+
26
+ **Compact extension tools** appears only while **Tool view** is Compact. Switching to Native hides it while retaining its value. Installing or removing the UI through the **Enabled** switch is deferred until the dialog closes. Existing configuration files need no migration.
27
+
28
+ ### General options
29
+
30
+ | Key | Default | Values and behavior |
31
+ | --- | --- | --- |
32
+ | `enabled` | `true` | Master switch for open-tui |
33
+ | `header` | `true` | Replaces the header; disable to leave that slot to Pi or another extension |
34
+ | `inlineFooter` | `false` | Moves the two main footer rows into a compatible editor's borders; extension status rows remain separate |
35
+ | `copyButtons` | `true` | Code-block and user-message Copy labels in fullscreen mode |
36
+ | `settingsLanguage` | `"en"` | `"en"` or `"zh"`; changes the settings interface language |
37
+ | `cursorStyle` | `"block"` | `"block"`, `"bar"`, or `"underline"`; the latter two need terminal cursor-shape support |
38
+ | `icons.mode` | `"auto"` | `"auto"`, `"nerd"`, `"unicode"`, or `"ascii"`; see [Icons](#icons) |
39
+ | `thinkingPeek.lines` | `1` | `0` (off), `1`, or `2`; visible while Pi's Hide thinking setting is on and reasoning is streamed |
40
+ | `toolView.mode` | `"native"` | `"native"` or `"compact"`; selected when Pi creates each tool row |
41
+ | `toolView.extensionTools` | `false` | In compact mode, includes extension, MCP, and replacement tools |
42
+ | `toolView.exclude` | `[]` | Exact tool names that keep native rendering, including built-ins; edited in the JSON file only |
43
+
44
+ For example, `"toolView": { "mode": "compact", "extensionTools": true, "exclude": ["edit", "subagent_wait"] }` enables compact rendering while preserving those two tools' native cards. Use `/reload` or a new session to rebuild existing rows with the new tool-view settings.
45
+
46
+ ### Footer options
47
+
48
+ All keys below are booleans inside `footerSegments`.
49
+
50
+ | Key | Default | Content or effect |
51
+ | --- | --- | --- |
52
+ | `cwd` | `true` | Current working directory, shortened when needed |
53
+ | `hostname` | `false` | Short host name (before the first dot) with a server icon |
54
+ | `sessionName` | `false` | Session name, when one is set |
55
+ | `gitBranch` | `true` | Branch name, or `HEAD` and a short commit hash in detached HEAD state |
56
+ | `gitStatus` | `true` | Conflicted, deleted, modified, renamed, staged, untracked, stashed, and ahead/behind counts |
57
+ | `gitCommit` | `false` | Tag pointing at the detached HEAD commit; only used when `gitBranch` is on |
58
+ | `runtime` | `true` | Detected project runtime and its version when available |
59
+ | `context` | `true` | Context bar, percentage, and token usage |
60
+ | `tokens` | `true` | Cumulative input/output tokens and the latest assistant cache-hit percentage |
61
+ | `cost` | `true` | Cumulative session cost reported by Pi |
62
+ | `liveTps` | `true` | Estimated streaming speed beside the working timer |
63
+ | `extensionStatuses` | `true` | Separate `ctx.ui.setStatus()` rows, including MCP status |
64
+ | `extensionSegments` | `true` | Contributions embedded in the main rows through the [footer event API](./footer-segments.md) |
65
+ | `capitalizeProviderName` | `true` | Capitalizes the provider's first character; disable to retain original casing |
66
+ | `nerdIconSpacing` | `false` | Adds one blank cell after Nerd Font icons, including runtime icons |
67
+
68
+ The provider/model and working/done timer do not have separate visibility switches; `liveTps` controls only the speed estimate. `extensionSegments` controls visibility, so hidden contributions still receive updates and their lifecycle stays available. It is independent of `extensionStatuses`.
69
+
70
+ ### Telemetry options
71
+
72
+ All keys below are booleans inside `telemetry` and default to `true`.
73
+
74
+ | Key | Effect |
75
+ | --- | --- |
76
+ | `enabled` | Master switch for the end-of-run notification |
77
+ | `tps` | Provider-based output tokens per second |
78
+ | `ttft` | Time to first output |
79
+ | `duration` | Full agent-run duration |
80
+ | `tokens` | Assistant input/output counts for measured turns |
81
+ | `stalls` | Stream pauses, shown only when detected |
82
+ | `cost` | Effective dollar rate per million reported tokens |
83
+
84
+ The footer's live-speed estimate has its own `footerSegments.liveTps` switch. See [usage and telemetry](./implementation.md#usage-and-telemetry) for measurement definitions and limitations.
85
+
86
+ ### Icons
87
+
88
+ | `icons.mode` | Selection |
89
+ | --- | --- |
90
+ | `auto` | ASCII when stdout is not a TTY, `TERM=dumb`, or the first configured locale (`LC_ALL`, `LC_CTYPE`, `LANG`) is not UTF-8. Otherwise Unicode when `SSH_TTY` or `SSH_CONNECTION` is set, and Nerd Font locally. An unset locale passes the UTF-8 check |
91
+ | `nerd` | Always use Nerd Font glyphs |
92
+ | `unicode` | Portable Unicode symbols; emoji use the terminal's own fallback font |
93
+ | `ascii` | Plain-text symbols |
94
+
95
+ Auto detection does not inspect installed fonts. Configure a Nerd Font in the terminal profile or choose `unicode`/`ascii` if symbols are missing. If the SSH client terminal has a Nerd Font, select `nerd` explicitly.
96
+
97
+ For wide Nerd glyphs that consume the gap before a label, enable **Footer → Other → Extra space after Nerd icons** (`footerSegments.nerdIconSpacing`). This affects regular and inline footers; Unicode/ASCII icons and compact Git flags are unchanged. In Kitty, `narrow_symbols U+E000-U+F8FF 1` in `kitty.conf` is an alternative.
98
+
99
+ ## 简体中文
100
+
101
+ 安装和功能示例见 [README](../README.zh-CN.md)。本参考列出 `open-tui.json` 接受的全部设置,依据为 [config.ts](../extensions/open-tui/config.ts)、[settings-command.ts](../extensions/open-tui/settings-command.ts) 和 [icons.ts](../extensions/open-tui/icons.ts)。
102
+
103
+ ### 存储与加载
104
+
105
+ 配置位于 Pi 的 agent 目录,默认路径为 `~/.pi/agent/open-tui.json`;设置 `PI_CODING_AGENT_DIR` 后则使用 `<PI_CODING_AGENT_DIR>/open-tui.json`。文件不存在且目录可写时,扩展会创建默认配置。
106
+
107
+ 只需填写要修改的值,缺失字段会补入[默认值](#default-json)。布尔开关应使用 JSON 布尔值,不能写成 `"false"` 这样的字符串。已知字段的无效值会回退到默认值;无法读取或解析 JSON 时,会提示警告并使用默认配置。直接编辑文件后,执行 `/reload` 或新开会话加载。如果从 `/open-tui` 保存失败,新值仍在当前会话生效,并提示检查目录权限和可用空间。
108
+
109
+ ### 设置窗口
110
+
111
+ | 页面 | 分组 |
112
+ | --- | --- |
113
+ | 常规 | 扩展、界面、消息与工具、底栏 |
114
+ | 外观 | 图标、输入框 |
115
+ | 底栏 | 位置、Git、运行环境、用量、其他 |
116
+ | 遥测 | 启用(独立总开关)、测量指标 |
117
+
118
+ Tab/Shift+Tab 或 ←/→ 切页,↑/↓ 或 Page Up/Page Down 移动,Enter/Space 修改值,Esc/q 关闭。暗色分组标题占一行,但不能选中或修改;滚动位置同时统计标题行和设置行。
119
+
120
+ **紧凑显示扩展工具**仅在**工具视图**为紧凑时显示;切回原生后隐藏,但保留开关值。通过**启用**开关安装或移除界面会延迟到窗口关闭后执行。现有配置文件无需迁移。
121
+
122
+ ### 常规选项
123
+
124
+ | 键 | 默认值 | 可选值与行为 |
125
+ | --- | --- | --- |
126
+ | `enabled` | `true` | open-tui 总开关 |
127
+ | `header` | `true` | 替换顶栏;关闭后把位置留给 Pi 或其他扩展 |
128
+ | `inlineFooter` | `false` | 将两条主底栏行移入兼容编辑器的边框,扩展状态行保持独立 |
129
+ | `copyButtons` | `true` | 全屏模式中代码块和用户消息的复制标签 |
130
+ | `settingsLanguage` | `"en"` | `"en"` 或 `"zh"`,控制设置界面的语言 |
131
+ | `cursorStyle` | `"block"` | `"block"`、`"bar"` 或 `"underline"`,后两种需要终端支持光标形状控制 |
132
+ | `icons.mode` | `"auto"` | `"auto"`、`"nerd"`、`"unicode"` 或 `"ascii"`,见[图标](#图标) |
133
+ | `thinkingPeek.lines` | `1` | `0`(关闭)、`1` 或 `2`;Pi 开启 Hide thinking 且模型输出推理时可见 |
134
+ | `toolView.mode` | `"native"` | `"native"` 或 `"compact"`,每条工具行创建时确定 |
135
+ | `toolView.extensionTools` | `false` | 紧凑模式下包含扩展、MCP 和替换内置工具的扩展 |
136
+ | `toolView.exclude` | `[]` | 保持原生渲染的完整工具名,可包含内置工具;仅在 JSON 文件中编辑 |
137
+
138
+ 例如 `"toolView": { "mode": "compact", "extensionTools": true, "exclude": ["edit", "subagent_wait"] }` 会启用紧凑模式,并让这两个工具保留原生卡片。执行 `/reload` 或新开会话,可让已有工具行按新设置重建。
139
+
140
+ ### 底栏选项
141
+
142
+ 下面各键均为 `footerSegments` 内的布尔值。
143
+
144
+ | 键 | 默认值 | 内容或作用 |
145
+ | --- | --- | --- |
146
+ | `cwd` | `true` | 当前工作目录,必要时缩短 |
147
+ | `hostname` | `false` | 主机短名称,即第一个点之前的部分,配合服务器图标 |
148
+ | `sessionName` | `false` | 会话名称,仅在已设置名称时显示 |
149
+ | `gitBranch` | `true` | 分支名称;分离 HEAD 时显示 `HEAD` 和短提交哈希 |
150
+ | `gitStatus` | `true` | 冲突、删除、修改、重命名、已暂存、未跟踪、stash 和 ahead/behind 数量 |
151
+ | `gitCommit` | `false` | 指向分离 HEAD 提交的标签,仅在 `gitBranch` 开启时使用 |
152
+ | `runtime` | `true` | 检测到的项目运行环境,以及可获取时的版本号 |
153
+ | `context` | `true` | 上下文进度条、百分比和 Token 用量 |
154
+ | `tokens` | `true` | 累计输入/输出 Token,以及最新助手回复的缓存命中率 |
155
+ | `cost` | `true` | Pi 报告的会话累计费用 |
156
+ | `liveTps` | `true` | 工作计时器旁的流式速度估算 |
157
+ | `extensionStatuses` | `true` | 独立的 `ctx.ui.setStatus()` 状态行,包括 MCP 状态 |
158
+ | `extensionSegments` | `true` | 扩展通过[底栏事件接口](./footer-segments.md)嵌入主信息行的内容 |
159
+ | `capitalizeProviderName` | `true` | 将服务商名称首字母大写;关闭后保留原始大小写 |
160
+ | `nerdIconSpacing` | `false` | Nerd Font 图标后额外留一格,包括运行环境图标 |
161
+
162
+ 服务商/模型和工作/完成计时器没有独立显示开关,`liveTps` 仅控制速度估算。`extensionSegments` 只控制可见性,隐藏内容仍接收更新,生命周期仍保持可用;它与 `extensionStatuses` 相互独立。
163
+
164
+ ### 遥测选项
165
+
166
+ 下面各键均为 `telemetry` 内的布尔值,默认全部为 `true`。
167
+
168
+ | 键 | 作用 |
169
+ | --- | --- |
170
+ | `enabled` | 运行结束通知的总开关 |
171
+ | `tps` | 基于服务商用量的每秒输出 Token |
172
+ | `ttft` | 首次输出延迟 |
173
+ | `duration` | Agent 完整运行耗时 |
174
+ | `tokens` | 被统计轮次的助手输入/输出数量 |
175
+ | `stalls` | 流式输出停顿,仅检测到时显示 |
176
+ | `cost` | 每百万报告 Token 的综合美元费率 |
177
+
178
+ 底栏实时速度使用独立的 `footerSegments.liveTps` 开关。指标定义和限制见[用量与遥测](./implementation.md#用量与遥测)。
179
+
180
+ ### 图标
181
+
182
+ | `icons.mode` | 选择规则 |
183
+ | --- | --- |
184
+ | `auto` | stdout 不是 TTY、`TERM=dumb`,或首个已设置的 locale(依次为 `LC_ALL`、`LC_CTYPE`、`LANG`)不是 UTF-8 时使用 ASCII。否则,存在 `SSH_TTY` 或 `SSH_CONNECTION` 时使用 Unicode,本地使用 Nerd Font。未设置 locale 时通过 UTF-8 检查 |
185
+ | `nerd` | 始终使用 Nerd Font 字形 |
186
+ | `unicode` | 可移植 Unicode 图标,emoji 使用终端自身的回退字体 |
187
+ | `ascii` | 纯文本图标 |
188
+
189
+ 自动检测不会检查已安装字体。图标缺失时,请在终端配置中选中 Nerd Font,或切换到 `unicode`/`ascii`。SSH 客户端终端已配置 Nerd Font 时,可显式选择 `nerd`。
190
+
191
+ 如果较宽的 Nerd Font 图标占用了标签前的间隔,可开启**底栏 → 其他 → Nerd 图标后额外留空**(`footerSegments.nerdIconSpacing`)。普通和内嵌底栏都适用,Unicode/ASCII 图标和紧凑 Git 状态标记不受影响。Kitty 用户也可在 `kitty.conf` 中设置 `narrow_symbols U+E000-U+F8FF 1`。
192
+
193
+ ## Default JSON
194
+
195
+ The complete default configuration is shared by both language references. Set `settingsLanguage` to `"zh"` for the Chinese settings interface.
196
+
197
+ 以下为两种语言共用的完整默认配置。将 `settingsLanguage` 改为 `"zh"` 即可使用中文设置界面。
198
+
199
+ ```json
200
+ {
201
+ "enabled": true,
202
+ "header": true,
203
+ "inlineFooter": false,
204
+ "copyButtons": true,
205
+ "settingsLanguage": "en",
206
+ "cursorStyle": "block",
207
+ "icons": {
208
+ "mode": "auto"
209
+ },
210
+ "footerSegments": {
211
+ "cwd": true,
212
+ "hostname": false,
213
+ "sessionName": false,
214
+ "gitBranch": true,
215
+ "gitStatus": true,
216
+ "gitCommit": false,
217
+ "runtime": true,
218
+ "context": true,
219
+ "tokens": true,
220
+ "cost": true,
221
+ "liveTps": true,
222
+ "extensionSegments": true,
223
+ "extensionStatuses": true,
224
+ "capitalizeProviderName": true,
225
+ "nerdIconSpacing": false
226
+ },
227
+ "telemetry": {
228
+ "enabled": true,
229
+ "tps": true,
230
+ "ttft": true,
231
+ "duration": true,
232
+ "tokens": true,
233
+ "stalls": true,
234
+ "cost": true
235
+ },
236
+ "thinkingPeek": {
237
+ "lines": 1
238
+ },
239
+ "toolView": {
240
+ "mode": "native",
241
+ "extensionTools": false,
242
+ "exclude": []
243
+ }
244
+ }
245
+ ```
@@ -0,0 +1,121 @@
1
+ # Extension footer segments
2
+
3
+ [Project overview](../README.md) · [Configuration](./configuration.md#english) · [Implementation and compatibility](./implementation.md#english)
4
+
5
+ Extensions use `pi.events` to add text to either main footer row, including the editor borders when `inlineFooter` is enabled. No additional status row is created.
6
+
7
+ ## Events
8
+
9
+ | Event | Payload | Purpose |
10
+ | --- | --- | --- |
11
+ | `open-tui:footer:set` | `{ lifecycle, key, position, text, compactText?, order?, priority? }` | Insert or completely replace the entry with this key |
12
+ | `open-tui:footer:remove` | `{ lifecycle, key }` | Remove an entry; a missing key has no effect |
13
+ | `open-tui:footer:query` | `undefined` | Request current availability |
14
+ | `open-tui:footer:state` | `{ available: true, lifecycle: string }` or `{ available: false }` | Announce availability changes and answer queries |
15
+
16
+ Use a nonempty, extension-prefixed `key`, such as `system-memory:usage`. `text` and optional `compactText` are strings; supported ANSI colors are preserved, while terminal commands and line breaks are filtered. Empty or style-only text is invisible. Malformed messages, unknown anchors and stale lifecycles are ignored; extra unknown fields are tolerated.
17
+
18
+ `set` replaces all fields, so omitted optional fields return to their defaults. Event names and payloads have no version number; future additions must remain compatible.
19
+
20
+ ## Placement and width
21
+
22
+ Choose exactly one `position` form:
23
+
24
+ | Position | Meaning |
25
+ | --- | --- |
26
+ | `{ row: 1, side: "left", edge: "end" }` | Explicit region: row `1` or `2`, side `"left"` or `"right"`, optional edge `"start"` or `"end"` (default) |
27
+ | `{ before: "git" }` | Before a built-in anchor |
28
+ | `{ after: "cost" }` | After a built-in anchor |
29
+
30
+ In inline mode, rows 1 and 2 become the top and bottom editor borders. Explicit regions stay on their chosen side. Relative positions follow the anchor; all anchors remain usable when their built-in content is hidden or absent. Their order and location are:
31
+
32
+ | Mode | Row 1 left | Row 1 right | Row 2 left | Row 2 right |
33
+ | --- | --- | --- | --- | --- |
34
+ | Regular | cwd, hostname, sessionName, git, runtime, timer | context | model | tokens, cost |
35
+ | Inline | sessionName, git | cwd, hostname, runtime, timer, context | model | tokens, cost |
36
+
37
+ `tokens` represents the complete token statistics group. Region/start entries precede its anchors, and region/end entries follow them. At each anchor, entries appear before or after its text. Within each placement, `order` (default `0`) sorts ascending, then `key` uses ordinary string ordering, independent of load order or locale.
38
+
39
+ `priority` defaults to `-1`; higher values survive longer when space is limited. Both `order` and `priority` must be finite numbers. Built-in priorities are `cwd: 4`, `hostname/timer: 3`, `sessionName: 2`, `git: 1`, `runtime/context: 0` on row 1, and `tokens: 2`, `cost: 1`, `model: 0` on row 2.
40
+
41
+ Both sides share the available row width. Shorter, nonempty `compactText` forms are tried in ascending priority order before truncating or removing lower-priority content; ties follow display order. A row without visible contributions retains its existing layout.
42
+
43
+ ## Lifecycle and visibility
44
+
45
+ 1. Register the `state` listener in your extension factory. Save the context in `session_start`, then emit `query`. Ignore announcements before initialization and repeated announcements for the same lifecycle.
46
+ 2. On a new available lifecycle, publish current content using that identifier. It identifies the current footer mount, not a protocol version. Disabling open-tui, replacing its footer or ending the session invalidates it and clears contributions.
47
+ 3. For asynchronous data, capture the lifecycle before the request and discard obsolete results. If data depends on a provider/model, also compare that identity and a request sequence. Stop pending work on shutdown; never publish through a Pi instance whose runtime has been replaced.
48
+
49
+ `footerSegments.extensionSegments` defaults to `true` and controls visibility only: hidden entries still receive updates, and availability and lifecycle stay unchanged. `footerSegments.extensionStatuses` separately controls normal `ctx.ui.setStatus()` rows. A contributor can use those rows as fallback while unavailable, clearing its own fallback when publishing a footer segment.
50
+
51
+ ## System memory example
52
+
53
+ Save as `system-memory.ts` and run `pi -e ./system-memory.ts` with pi-open-tui installed. This reads real system memory every five seconds, places it after cost and handles fallback and cleanup.
54
+
55
+ ```typescript
56
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
57
+ import { freemem, totalmem } from "node:os";
58
+
59
+ export default function (pi: ExtensionAPI) {
60
+ const key = "system-memory:usage";
61
+ let ctx: ExtensionContext | undefined;
62
+ let lifecycle: string | undefined;
63
+ let timer: ReturnType<typeof setInterval> | undefined;
64
+
65
+ const publish = () => {
66
+ if (!ctx) return;
67
+ const total = totalmem();
68
+ const used = total - freemem();
69
+ const text = `RAM ${(used / 2 ** 30).toFixed(1)}/${(total / 2 ** 30).toFixed(1)} GiB`;
70
+ if (lifecycle === undefined) {
71
+ ctx.ui.setStatus(key, text);
72
+ return;
73
+ }
74
+ ctx.ui.setStatus(key, undefined);
75
+ pi.events.emit("open-tui:footer:set", {
76
+ lifecycle,
77
+ key,
78
+ position: { after: "cost" },
79
+ text,
80
+ compactText: `RAM ${Math.round(used / total * 100)}%`,
81
+ });
82
+ };
83
+
84
+ pi.events.on("open-tui:footer:state", (data) => {
85
+ if (!ctx || data === null || typeof data !== "object" || !("available" in data)) return;
86
+ if (data.available === true && "lifecycle" in data
87
+ && typeof data.lifecycle === "string" && data.lifecycle.length > 0) {
88
+ if (lifecycle === data.lifecycle) return;
89
+ lifecycle = data.lifecycle;
90
+ } else if (data.available === false) {
91
+ if (lifecycle === undefined) return;
92
+ lifecycle = undefined;
93
+ } else {
94
+ return;
95
+ }
96
+ publish();
97
+ });
98
+
99
+ pi.on("session_start", (_event, nextCtx) => {
100
+ if (!nextCtx.hasUI || nextCtx.mode !== "tui") return;
101
+ ctx = nextCtx;
102
+ publish();
103
+ pi.events.emit("open-tui:footer:query", undefined);
104
+ timer = setInterval(publish, 5_000);
105
+ timer.unref();
106
+ });
107
+
108
+ pi.on("session_shutdown", () => {
109
+ if (timer) clearInterval(timer);
110
+ timer = undefined;
111
+ const previousCtx = ctx;
112
+ const previousLifecycle = lifecycle;
113
+ ctx = undefined;
114
+ lifecycle = undefined;
115
+ if (previousLifecycle !== undefined) {
116
+ pi.events.emit("open-tui:footer:remove", { key, lifecycle: previousLifecycle });
117
+ }
118
+ previousCtx?.ui.setStatus(key, undefined);
119
+ });
120
+ }
121
+ ```
@@ -0,0 +1,185 @@
1
+ # Implementation and compatibility / 实现与兼容性
2
+
3
+ [English](#english) | [简体中文](#简体中文)
4
+
5
+ ## English
6
+
7
+ Feature examples and everyday controls are in the [README](../README.md). All settings and defaults are in the [configuration reference](./configuration.md#english). This document explains the measurements, rendering rules, and integration boundaries behind those features.
8
+
9
+ ### Lifecycle and data sources
10
+
11
+ [index.ts](../extensions/open-tui/index.ts) connects Pi events to the UI. At session start it loads settings and, when enabled in a TUI context, installs the footer, editor decoration, Copy-button integration, and the header if `header` is also enabled. Tool renderers are registered separately; configuration is loaded early when Pi rebuilds a resumed transcript before `session_start`.
12
+
13
+ The working timer requests a redraw every 250 ms while an agent run is active. Git and runtime reads capture a session generation; their results are discarded if the session has changed or shut down. Shutdown stops the timer, deactivates footer contributions, and removes the installed UI. Compact tool activity timers are stopped when execution ends or the session shuts down. See [session-lifecycle.ts](../extensions/open-tui/session-lifecycle.ts) for the generation guard.
14
+
15
+ - [git.ts](../extensions/open-tui/git.ts) parses `git status --porcelain=v1 --branch` and reads commit, tag, and stash information as requested. Each Git command has a two-second timeout. A failed status read produces an empty Git state. Turning off both branch and status display avoids the refresh; `gitCommit` only adds a tag to the detached branch display.
16
+ - [runtime.ts](../extensions/open-tui/runtime.ts) chooses the first matching definition from its ordered list of 50+ runtimes and environments. It checks project files, folders, extensions, and environment variables in the working directory. It reports the locally executed version command's result, not a declared dependency version. Version commands have a 2.5-second timeout; unavailable versions leave the runtime symbol visible. Results are cached by directory and runtime, with a fingerprint of relevant files and environment values.
17
+ - [state.ts](../extensions/open-tui/state.ts) reads model metadata and session usage. Usage is cached by session manager and leaf ID, with invalidation on message completion, compaction, tree changes, and session start.
18
+
19
+ ### Editor and layout
20
+
21
+ [editor.ts](../extensions/open-tui/editor.ts) decorates the same editor instance, retaining input callbacks, completion, and ghost text. It requires native-style borders plus focus, border-color, mouse, and padding capabilities. An initial render probes for the borders; missing capabilities, an unrecognized layout, or a failing probe leave the editor undecorated. Each framed render checks the borders again. If they disappear, that render uses the full requested width without frame mouse offsets; framing resumes when the borders return.
22
+
23
+ Factories registered through the current `ctx.ui.setEditorComponent()` API can run before or after open-tui. Runtime replacements are decorated when Pi mounts them, including replacements from a fresh shortcut context. Temporary dialogs are left alone. Cleanup restores owned methods and preserves the latest editor factory, including wrappers registered later. This ordering guarantee does not extend to header/footer replacement or extensions that bypass the current registration API with a saved setter or replace its methods.
24
+
25
+ Block mode uses the editor's existing cursor behavior. Bar and underline modes enable the terminal hardware cursor and request its shape with terminal escape sequences. Terminal support is required; cleanup restores the prior hardware-cursor visibility.
26
+
27
+ [footer.ts](../extensions/open-tui/footer.ts) and [utils.ts](../extensions/open-tui/utils.ts) fit content by visible terminal width. Without contributed segments, the first row tries compact forms (directory basename, context percentage, timer without live TPS) before shrinking or dropping runtime/context, then Git, session name, hostname/timer, and finally the directory. The second row prioritizes statistics over the model label.
28
+
29
+ Inline mode puts session name and Git on the top-left border; the directory starts the right-hand group, followed by host, runtime, timer, and context. Model details remain bottom-left, with token/cost data bottom-right. A compatible framed editor is needed; otherwise the footer stays separate. Extension status rows remain outside the frame and may wrap. Styled statuses preserve their ANSI colors, while unstyled statuses use the muted theme color; terminal commands and line breaks are filtered.
30
+
31
+ Contributed main-row segments use the shared width and priority rules in the [footer API reference](./footer-segments.md#placement-and-width). Hiding a built-in field does not remove its anchor. Footer replacement, disablement, or session shutdown invalidates the contribution lifecycle; contributors must republish for a new lifecycle.
32
+
33
+ ### Usage and telemetry
34
+
35
+ Footer totals include assistant messages, usage reported by tool results, standalone usage entries such as cache warming, compaction, and branch summaries. Input is displayed as uncached input plus cache reads: `U = input + cacheWrite`, `R = cacheRead`, for example `12k (U 2k + R 10k)`. Cost sums the recorded `usage.cost.total` values.
36
+
37
+ The cache-hit percentage uses the latest assistant response: `cacheRead / (input + cacheRead + cacheWrite) × 100`. It is not a cumulative hit rate and is omitted when cache usage has never been reported or the latest response lacks a usable denominator.
38
+
39
+ [telemetry.ts](../extensions/open-tui/telemetry.ts) combines measured assistant turns into one notification at `agent_settled`:
40
+
41
+ | Field | Calculation and limits |
42
+ | --- | --- |
43
+ | TPS | Sum of provider-reported assistant output tokens divided by summed generation seconds, from each `turn_start` to the assistant `message_end`. Includes TTFT, reasoning, buffering, and stalls; excludes tool execution between turns. Displays `—` without output tokens or measurable generation time |
44
+ | TTFT | First measured turn's start to its first nonempty text, thinking, or tool-call-argument delta. If only final output usage arrives, message completion is the available first-output timestamp |
45
+ | Duration | Elapsed time from `agent_start` to `agent_settled`, including tools |
46
+ | Tokens | Assistant usage in measured turns, with input separated into uncached (`input + cacheWrite`) and cache reads |
47
+ | Stalls | Gaps of at least one second between nonempty stream updates after output begins. Consecutive qualifying gaps form one stall until a shorter gap occurs. The entire gap contributes to stall time; shown only when detected |
48
+ | `$ / M` | `sum(usage.cost.total) / (sum(usage.totalTokens) / 1,000,000)`. An effective rate combining the reported input, output, and cache prices, not the session's total spend. Omitted without positive cost and token totals |
49
+
50
+ A turn produces no measurement when it lacks a completed assistant message or observed output. Observed output means a nonempty streamed text, thinking, or tool-call delta, or a positive final output usage value. A run with no measured turns produces no notification. Provider buffering and missing usage data limit what the extension can report. The notification covers assistant turns, whereas footer totals include the additional session usage sources above.
51
+
52
+ Live TPS uses streamed text, thinking, and tool-call arguments over a trailing two-second window. It estimates roughly four characters per token for Latin text/code and one token per CJK character. A stream pause makes the rate fall toward zero; during tool execution and between messages the last rate remains dimmed. It resets when the run settles. This estimate has its own `footerSegments.liveTps` switch and can differ from the provider-based final TPS.
53
+
54
+ ### Thinking labels
55
+
56
+ [peek.ts](../extensions/open-tui/peek.ts) tracks idle, thinking, and done phases. It extracts the latest nonempty reasoning lines, normalizes whitespace, and strips terminal control sequences before display. The label is built using the width of the actual message component, so a layout extension can make it narrower than the terminal.
57
+
58
+ In two-line mode, a long latest logical line can occupy both rows; otherwise the previous and latest lines share the same indentation. Clipping retains the newest tail and respects visible width and grapheme boundaries. Answer text or assistant-message completion freezes the label at a check mark. After settlement, the native label returns after a short delay guarded against clearing a newer task's label. Pi's own Hide thinking toggle controls visibility.
59
+
60
+ ### Tool rendering
61
+
62
+ [tool-view.ts](../extensions/open-tui/tool-view.ts) registers rendering wrappers, leaving execution, arguments, results, and model-visible content unchanged. Built-in summaries are selected only when Pi reports the tool source as `builtin`. A same-name replacement keeps its native renderer unless compact mode and `toolView.extensionTools` are both enabled, in which case it uses extension handling. `toolView.exclude` takes precedence over either category.
63
+
64
+ Successful rows have dim text and a green dot. Pending dots are dim; running dots alternate between accent and dim once per second. Errors retain a red dot and summary. Shell output shows the latest three nonempty lines while running; failures retain the closing status and last output line. Known tool truncation notices are summarized without discarding arbitrary bracketed text or error messages.
65
+
66
+ Completed rows reuse rendered lines until width, theme, icons, or invalidation changes them. Calls remain independent. Expansion uses the renderers Pi resolves, with generic argument/output components only for missing extension renderers. Inline images remain visible while collapsed because Pi renders them below the row.
67
+
68
+ #### Edit and write previews
69
+
70
+ [tool-preview.ts](../extensions/open-tui/tool-preview.ts) parses Pi's actual edit diff. Malformed diffs have no guessed preview. Old and new code are syntax-highlighted separately, with red/green backgrounds for removed/added lines. Adjacent removed/added groups are paired for stronger changed-word highlighting when they share enough content.
71
+
72
+ Word matching is bounded to an edit distance of 256. Lines up to 512 characters have no wall-clock timeout, so scheduling delays do not remove their word highlights. Longer lines up to 4,096 characters use a five-millisecond timeout; larger or unmatched lines keep whole-line highlighting. Syntax highlighting has a separate input-size budget.
73
+
74
+ Write previews use the submitted content with line numbers on the normal background. New files and overwrites look the same; no old-file diff is synthesized. Only successful completed edit/write results get previews. Bodies show at most 16 rows, including context and ellipsis rows, followed by a hidden-row count and `Ctrl+O` hint. Compact previews truncate long lines with `…`; expanded rendering retains Pi's native behavior.
75
+
76
+ #### Extension and FFF summaries
77
+
78
+ With `toolView.extensionTools` enabled, unknown tools use generic summaries: nonempty text-line counts, image counts, duration, and the first text line. They do not receive edit/write previews. Truncation requires `details.truncation.truncated: true`; a valid total line count may replace the displayed count, but built-in-specific limit fields are not interpreted. Running rows show elapsed time even before a first result arrives.
79
+
80
+ Native extension renderers start when a row is expanded. After collapse, hidden progress updates are skipped, but completion and error updates still reach previously initialized renderers so their timers can stop. Put tools that depend on native progress behavior in `toolView.exclude`.
81
+
82
+ Recognized `@ff-labs/pi-fff` installations use search summaries. `ffgrep` and `fff-multi-grep` report this page's match count; `fffind` reports the total matching file count. These rules also cover FFF's `grep`/`find`/`multi_grep` override names. Summaries preserve pagination, fuzzy and regex-to-literal fallback, and capped weak-match hints; indexed file counts are not treated as matches.
83
+
84
+ Recognition checks Pi's npm source metadata, the installed npm entry path, or the official `dmtrKovalenko/fff` GitHub source with its `packages/pi-fff/src/index.ts` entry point. Unrecognized sources or result metadata use the generic summary. Expansion preserves FFF's native output and its existing line-length limits.
85
+
86
+ ### Copy-button behavior
87
+
88
+ [copy-buttons.ts](../extensions/open-tui/copy-buttons.ts) decorates Markdown code blocks and user messages in fullscreen mode. At a code block's rendering width below 8 columns, native fences are retained; from 8 through 15 columns, the frame has no label. A completed block can show `[Copy]` from 16 columns. Long code lines wrap inside the frame. The top/bottom borders replace the fence rows, and the label adds no extra row. Thinking blocks retain native fences; streaming blocks have a frame without a label.
89
+
90
+ Each Markdown owner tracks block descriptors and occurrence-specific copy regions. Marker data survives Pi's block cache and is removed before display; occurrence matching restores source tabs that Pi normalizes for rendering. A policy change, such as streaming completion, invalidates the Markdown cache so labels appear without resizing. Repeated identical blocks still copy their own source occurrence.
91
+
92
+ The user-message label occupies the bubble's last row and copies the original message. Code labels copy code without fences. Ordinary selection is passed to Pi; a qualifying left-button release on a recorded Copy press copies the text. Moving, removing, or changing a target cancels a held click, as do disabling or cleaning up the installation. Clipboard completion from a cleaned-up installation does not flash a notification.
93
+
94
+ Some integrations use Pi component internals: Copy buttons check required Markdown and fullscreen mouse methods before installing; per-message thinking labels and editor mounting also inspect component structure. Other extensions that patch the same renderers can affect compatibility. Cleanup restores methods still owned by open-tui and leaves later wrappers in place.
95
+
96
+ ## 简体中文
97
+
98
+ 功能示例和日常操作见 [README](../README.zh-CN.md),全部设置与默认值见[配置参考](./configuration.md#简体中文)。本文说明这些功能背后的指标口径、渲染规则和集成边界。
99
+
100
+ ### 生命周期与数据来源
101
+
102
+ [index.ts](../extensions/open-tui/index.ts) 将 Pi 事件接入界面。会话开始时读取配置;在已启用的 TUI 环境中安装底栏、编辑器装饰和复制按钮,并在 `header` 也开启时安装顶栏。工具渲染器单独注册;恢复会话时,Pi 可能在 `session_start` 之前重建记录,因此会提前读取配置。
103
+
104
+ Agent 运行期间,工作计时器每 250 毫秒请求一次重绘。Git 和运行环境读取会记录发起时的会话代次,如果会话已切换或关闭,结果会被丢弃。关闭会话会停止计时器、停用底栏贡献并移除已安装的界面;紧凑工具的动画计时器会在执行结束或会话关闭时停止。代次保护见 [session-lifecycle.ts](../extensions/open-tui/session-lifecycle.ts)。
105
+
106
+ - [git.ts](../extensions/open-tui/git.ts) 解析 `git status --porcelain=v1 --branch`,按需读取提交、标签和 stash。每条 Git 命令的超时为两秒,状态读取失败时返回空 Git 状态。分支和状态显示都关闭时跳过刷新;`gitCommit` 仅为分离 HEAD 的分支显示追加标签。
107
+ - [runtime.ts](../extensions/open-tui/runtime.ts) 从按顺序排列的 50 多种运行环境定义中选择第一个匹配项,检查工作目录中的项目文件、目录、扩展名和环境变量。版本来自本地实际执行的版本命令,不是依赖声明中的版本。命令超时为 2.5 秒,版本不可用时仍显示环境标记。结果按目录和环境缓存,并用相关文件与环境变量构成指纹。
108
+ - [state.ts](../extensions/open-tui/state.ts) 读取模型信息与会话用量。用量缓存绑定会话管理器和当前叶节点 ID,在消息结束、压缩、会话树变化和会话开始时失效。
109
+
110
+ ### 编辑器与布局
111
+
112
+ [editor.ts](../extensions/open-tui/editor.ts) 直接装饰同一个编辑器实例,保留输入回调、补全和灰字建议。它要求编辑器保留原生风格的边界,以及焦点、边框颜色、鼠标和内边距接口。初次渲染会检测边界;能力缺失、布局不符或检测抛错时,不加装饰。之后每次加框也会重新检查;边界消失时,该次渲染使用完整请求宽度且不偏移鼠标坐标,边界恢复后继续加框。
113
+
114
+ 通过当前 `ctx.ui.setEditorComponent()` 接口注册的工厂可以在 open-tui 之前或之后执行。运行时替换在 Pi 挂载编辑器时装饰,也支持来自新快捷键上下文的替换;临时对话框保持原样。清理时恢复自己拥有的方法,保留最新的编辑器工厂和后注册的包装。该顺序保证不涵盖顶栏/底栏替换,也不涵盖通过旧 setter 绕过当前注册接口或直接替换接口方法的扩展。
115
+
116
+ 块状模式保留编辑器现有光标行为;竖线和下划线模式开启终端硬件光标,并通过终端转义序列请求形状,因此需要终端支持。清理时恢复先前的硬件光标可见性。
117
+
118
+ [footer.ts](../extensions/open-tui/footer.ts) 与 [utils.ts](../extensions/open-tui/utils.ts) 按终端可见宽度排版。没有扩展嵌入片段时,第一行先尝试目录末级名称、上下文百分比、去除实时 TPS 的计时器等紧凑形式,再缩短或移除运行环境/上下文、Git、会话名、主机名/计时器,最后才是当前目录。第二行优先保留统计数据,再缩短模型标签。
119
+
120
+ 内嵌模式将会话名和 Git 放在上边框左侧,右侧从当前目录开始,接主机名、运行环境、计时器和上下文;下边框左侧为模型,右侧为 Token/费用。需要兼容且已加框的编辑器,否则底栏独立显示。扩展状态行始终在边框外,可换行。已设置样式的状态保留 ANSI 颜色,无样式内容使用 muted 主题色,终端命令和换行会被过滤。
121
+
122
+ 主信息行中的扩展片段使用[底栏接口文档](./footer-segments.md#placement-and-width)规定的共享宽度与优先级。隐藏内置字段不会移除其锚点;底栏被替换、扩展被关闭或会话结束时,贡献生命周期失效,提供内容的扩展必须在新生命周期重新发布。
123
+
124
+ ### 用量与遥测
125
+
126
+ 底栏总计包含助手消息、工具结果报告的用量、缓存预热等独立用量记录、压缩和分支摘要。输入区分未缓存输入与缓存读取:`U = input + cacheWrite`,`R = cacheRead`,例如 `12k (U 2k + R 10k)`。费用对记录中的 `usage.cost.total` 求和。
127
+
128
+ 缓存命中率使用最近一条助手回复的 `cacheRead / (input + cacheRead + cacheWrite) × 100`,不表示累计命中率。没有报告过缓存用量,或最新回复缺少有效分母时不显示。
129
+
130
+ [telemetry.ts](../extensions/open-tui/telemetry.ts) 将可测量的助手轮次合并,在 `agent_settled` 时生成一条通知:
131
+
132
+ | 字段 | 计算方式与限制 |
133
+ | --- | --- |
134
+ | TPS | 服务商报告的助手输出 Token 总量除以生成秒数总和,每轮从 `turn_start` 计到助手 `message_end`。包含 TTFT、推理、缓冲和停顿,不包含轮次之间的工具执行;无输出 Token 或有效生成时间时显示 `—` |
135
+ | TTFT | 第一个被统计轮次开始,到首次非空正文、思考或工具参数增量的时间。如果只在结束时收到输出用量,则以消息结束时间作为可用的首次输出时间 |
136
+ | 耗时 | 从 `agent_start` 到 `agent_settled` 的时间,包含工具执行 |
137
+ | Token | 被统计轮次中的助手用量,输入区分未缓存(`input + cacheWrite`)和缓存读取 |
138
+ | 停顿 | 输出开始后,非空流式更新之间至少一秒的间隔。连续满足条件的间隔归为一次停顿,直到出现更短间隔;停顿时长包含整个间隔,仅检测到时显示 |
139
+ | `$ / M` | `sum(usage.cost.total) / (sum(usage.totalTokens) / 1,000,000)`,综合输入、输出和缓存报告价格的费率,不是会话总费用;缺少正数费用或 Token 总量时不显示 |
140
+
141
+ 缺少完成的助手消息或可识别的输出时,该轮不会产生统计。输出包括非空的流式文本、思考或工具调用增量,也包括正数的最终输出用量。没有可统计轮次的运行不显示通知。服务商缓冲输出和用量字段缺失会限制可测结果。通知统计助手轮次,底栏还包含前述其他会话用量来源。
142
+
143
+ 实时 TPS 根据最近两秒的流式正文、思考和工具调用参数估算,拉丁文本/代码按约四个字符一个 Token,中日韩字符按每字一个 Token。流式停顿时速度降向零,工具执行期间和消息之间以暗色保留上一数值,任务结束后重置。它使用独立的 `footerSegments.liveTps` 开关,数值可能与基于服务商用量的最终 TPS 不同。
144
+
145
+ ### 思考标签
146
+
147
+ [peek.ts](../extensions/open-tui/peek.ts) 跟踪空闲、思考中和完成三种状态,提取最近的非空推理行、规范空白,并在显示前去除终端控制序列。标签按实际消息组件宽度生成,因此其他布局扩展缩窄消息区域时也会适配。
148
+
149
+ 双行模式中,如果最新逻辑行很长,会占用两行;否则上一行和最新一行保持相同缩进。截断保留最新末尾,并考虑可见宽度和完整字素边界。正文开始或助手消息结束后,标签变为对勾。任务结束后稍作延迟恢复原生标签,并防止旧任务的清理误清新任务标签。可见性仍由 Pi 自身的 Hide thinking 控制。
150
+
151
+ ### 工具渲染
152
+
153
+ [tool-view.ts](../extensions/open-tui/tool-view.ts) 注册渲染包装,不修改执行、参数、结果或模型可见内容。仅在 Pi 将工具来源标记为 `builtin` 时使用内置摘要。同名替换工具默认保留原生渲染;只有紧凑模式和 `toolView.extensionTools` 同时开启时才使用扩展处理。`toolView.exclude` 优先于这两类规则。
154
+
155
+ 成功行使用暗色文字和绿色圆点,等待圆点变暗,运行圆点每秒在强调色与暗色间切换,错误保留红色圆点和摘要。Shell 运行中显示最新三条非空输出行,失败时保留结束状态与最后一行输出。已知的工具截断通知会归纳为提示,不会把任意方括号正文或错误信息一并删除。
156
+
157
+ 完成的行复用渲染结果,直到宽度、主题、图标变化或缓存失效。各次调用独立显示。展开使用 Pi 解析出的渲染器,仅在扩展缺少渲染器时补上通用参数/输出组件。折叠时内联图片仍可见,因为 Pi 将其绘制在行下方。
158
+
159
+ #### 编辑与写入预览
160
+
161
+ [tool-preview.ts](../extensions/open-tui/tool-preview.ts) 解析 Pi 的真实编辑 diff,格式无效时不猜测预览。旧代码和新代码分别做语法高亮,删除/新增行使用红/绿背景。相邻的删除/新增行组会配对,共享内容足够时进一步高亮变更词。
162
+
163
+ 词差异匹配的编辑距离上限为 256。长度不超过 512 个字符的行不使用时钟超时,因此调度延迟不会让词高亮消失;更长且不超过 4,096 个字符的行使用五毫秒超时,更大或无法匹配的行保留整行高亮。语法高亮另有输入长度上限。
164
+
165
+ 写入预览基于提交内容,在普通背景上显示行号;新建与覆盖相同,不生成旧文件 diff。仅完成且成功的编辑/写入会产生预览。正文最多 16 行,包含上下文和省略行,之后显示隐藏行数与 `Ctrl+O` 提示。紧凑预览用 `…` 截断长行,展开后保留 Pi 原生行为。
166
+
167
+ #### 扩展与 FFF 摘要
168
+
169
+ 开启 `toolView.extensionTools` 后,未知工具使用通用摘要:非空文本行数、图片数、耗时和首行文本,不显示编辑/写入预览。截断要求明确的 `details.truncation.truncated: true`;有效总行数可以替代显示行数,不解释内置工具专用的上限字段。首次结果到达前,运行行也会显示耗时。
170
+
171
+ 扩展原生渲染器在展开时开始运行;再次折叠后跳过隐藏进度更新,但完成和错误仍会传给已初始化的渲染器,以便停止自身计时器。依赖原生进度行为的工具可列入 `toolView.exclude`。
172
+
173
+ 识别到的 `@ff-labs/pi-fff` 安装使用搜索摘要:`ffgrep` 和 `fff-multi-grep` 显示当前页匹配数,`fffind` 显示总匹配文件数,也适用于 FFF 的 `grep`/`find`/`multi_grep` 替换名称。摘要保留分页、模糊搜索、正则转字面搜索和弱匹配限制提示;索引文件总数不会当作命中数。
174
+
175
+ 识别依据是 Pi 的 npm 来源元数据、已安装 npm 包的入口路径,或官方 `dmtrKovalenko/fff` GitHub 来源及其 `packages/pi-fff/src/index.ts` 入口。来源或结果元数据无法识别时使用通用摘要;展开保留 FFF 原生输出及其已有行长度限制。
176
+
177
+ ### 复制按钮行为
178
+
179
+ [copy-buttons.ts](../extensions/open-tui/copy-buttons.ts) 在全屏模式中装饰 Markdown 代码块和用户消息。代码块渲染宽度不足 8 列时保留原生围栏,8 至 15 列显示无标签边框,从 16 列起完成的代码块可显示 `[Copy]`。长行在边框内换行,上下边框替代围栏行,标签不额外占一行。思考块保留原生围栏,流式代码块只有边框。
180
+
181
+ 每个 Markdown 实例分别跟踪代码块描述和对应的复制区域。标记数据随 Pi 代码块缓存保留,在最终显示前移除;通过具体出现位置匹配源代码,恢复 Pi 渲染时规范化的制表符。流式结束等显示规则变化会使 Markdown 缓存失效,无需调整窗口大小即可显示标签。完全相同的重复代码块仍对应各自的源位置。
182
+
183
+ 用户消息标签位于气泡末行,复制消息原文;代码标签复制不含围栏的代码。普通选择交给 Pi 处理,只有从已记录复制区域按下并满足条件的鼠标左键释放才执行复制。目标移动、消失或变化会取消尚未完成的点击,关闭按钮或清理安装也会取消。已清理安装的异步剪贴板操作结束后不再弹出提示。
184
+
185
+ 部分集成依赖 Pi 组件内部结构:复制按钮安装前会检查所需 Markdown 和全屏鼠标方法,逐消息思考标签和编辑器挂载也会检查组件结构。其他扩展修改相同渲染器时可能影响兼容性。清理仅恢复仍由 open-tui 拥有的方法,保留后来添加的包装。