pi-better-btw-plus 1.2.0 → 1.3.1

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.
package/README.md CHANGED
@@ -77,6 +77,7 @@ Press `Esc` to close it. Reopen with `/btw` or `Alt+W` to continue where you lef
77
77
  | `Alt+N` | Start an empty conversation |
78
78
  | `Alt+E` | Export the transcript to `$CWD/.agents/eval/pi-better-btw-<timestamp>.md` |
79
79
  | `Ctrl+L` | Open the fork model picker (scoped + authenticated models; `↑/↓` select, `Enter` confirm, `Esc` cancel) |
80
+ | `Alt+Shift+C` | Copy the whole input editor text (paste markers expanded — exactly what a submit would send) |
80
81
 
81
82
  In Read-only Mode (default), the read-only lane is **enforced**: attempting an out-of-lane tool call is hard-blocked with a prompt injection; a second violation escalates the wording and aborts the turn (a `🚧 lane blocked` status line). Executed-but-failed read-only calls are re-grounded by an `afterToolCall` note. Edit mode (`Alt+T`) is unaffected.
82
83
 
@@ -121,6 +122,7 @@ What changed since I opened this side chat?
121
122
  | Double-click | Select the whole rendered line |
122
123
  | `Ctrl+C` / `Ctrl+Shift+C` | Copy the active mouse selection (the selection is kept until you click elsewhere, so repeated presses re-copy) |
123
124
  | `Ctrl+X` | Copy the last assistant message |
125
+ | `Alt+Shift+C` | Copy the whole input editor text (paste markers expanded — exactly what a submit would send; empty input shows `Input is empty`) |
124
126
  | `Ctrl+V` / `Alt+V` | Paste the system clipboard at the cursor (same normalization + `[paste #N …]` markers as right-click paste) |
125
127
  | Mouse right-click (chat area) | Copy the retained mouse selection (fires on release, keeps the highlight) |
126
128
  | Mouse right-click (input editor) | Paste the system clipboard at the cursor (editor normalization + `[paste #N …]` markers for large pastes) |
@@ -217,7 +219,7 @@ Structure:
217
219
  │ ├── side-chat-mouse.ts # minimal SGR mouse parsing
218
220
  │ ├── clipboard-read.ts # platform clipboard read (win32 / darwin / linux + OSC 52 fallback)
219
221
  │ ├── retry.ts # turn-level retry: classifyRetryable + runWithRetry
220
- │ ├── shortcuts.ts # hotkey bindings (Alt+W / Alt+T)
222
+ │ ├── shortcuts.ts # hotkey bindings (Alt+W bg / Alt+T mode / Alt+Shift+C copy input)
221
223
  │ ├── model-switch.ts # Ctrl+L fork model picker: list building + thinking clamp
222
224
  │ ├── side-chat-export.ts # Alt+E transcript export
223
225
  │ ├── tool-wrapper.ts # write-path overlap warnings
package/README.zh-CN.md CHANGED
@@ -79,6 +79,7 @@ pi install npm:pi-better-btw-plus
79
79
  | `Alt+N` | 开始空白对话 |
80
80
  | `Alt+E` | 导出对话记录到 `$CWD/.agents/eval/pi-better-btw-<timestamp>.md` |
81
81
  | `Ctrl+L` | 打开 fork 模型选择器(scoped + 已认证模型;`↑/↓` 选择,`Enter` 确认,`Esc` 取消) |
82
+ | `Alt+Shift+C` | 复制输入编辑器全部文本(paste 标记展开——与提交给 agent 的内容一致) |
82
83
 
83
84
  在Readonly Mode(default),只读车道是**强制的**:越权调用工具会被硬阻断并注入prompt;第二次违规会升级措辞并中止该轮,提示(`🚧 lane blocked` 状态行)。已执行但失败的只读调用会被 `afterToolCall` 备注再次归位。编辑模式(`Alt+T`)不受影响。
84
85
 
@@ -122,6 +123,7 @@ What changed since I opened this side chat?
122
123
  | 双击 | 选择整行 |
123
124
  | `Ctrl+C` / `Ctrl+Shift+C` | 复制当前鼠标选择(选择保留到下次点击,可重复复制) |
124
125
  | `Ctrl+X` | 复制最后一条 assistant 消息 |
126
+ | `Alt+Shift+C` | 复制输入编辑器全部文本(paste 标记展开——与提交给 agent 的内容一致;空输入提示 `Input is empty`) |
125
127
  | `Ctrl+V` / `Alt+V` | 把系统剪贴板粘贴到光标处(归一化 + `[paste #N …]` 标记,同右键粘贴) |
126
128
  | 鼠标右键(聊天区) | 复制保留的鼠标选择(松开时触发,保持高亮) |
127
129
  | 鼠标右键(输入框) | 把系统剪贴板粘贴到光标处(编辑器归一化 + 大段 `[paste #N …]` 标记) |
@@ -219,7 +221,7 @@ btw 上下文保留主线的 system prompt 于 system 槽位,并逐字注入 f
219
221
  │ ├── clipboard-read.ts # 平台剪贴板读取(win32 / darwin / linux + OSC 52 兜底)
220
222
  │ ├── retry.ts # turn 级重试引擎:classifyRetryable + runWithRetry
221
223
  │ ├── model-switch.ts # Ctrl+L fork 模型选择器:列表构建 + thinking 钳制
222
- │ ├── shortcuts.ts # 快捷键绑定(Alt+W / Alt+T
224
+ │ ├── shortcuts.ts # 快捷键绑定(Alt+W 后台 / Alt+T 模式 / Alt+Shift+C 复制输入)
223
225
  │ ├── side-chat-export.ts # Alt+E 对话导出
224
226
  │ ├── tool-wrapper.ts # 写路径重叠警告
225
227
  │ └── file-activity-tracker.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-btw-plus",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "pi extension: /btw side-chat overlay — maintained fork of @yceachan/pi-better-btw (+ right-click copy/paste, fork model switch, turn-level retry)",
5
5
  "type": "module",
6
6
  "keywords": [
package/srcs/shortcuts.ts CHANGED
@@ -6,6 +6,17 @@ export const SIDE_CHAT_SHORTCUT: KeyId = "alt+w";
6
6
  interface Keybinding {
7
7
  readonly keys: readonly KeyId[];
8
8
  readonly hint: string;
9
+ /**
10
+ * Raw terminal encodings that also trigger this action, besides the
11
+ * parser-recognizable `keys`. Needed because pi-tui's parser cannot express
12
+ * alt+shift+letter: its legacy branch only maps ESC+lowercase to alt+letter,
13
+ * and its modifyOtherKeys parse drops the shift bit. Terminals without the
14
+ * kitty keyboard protocol (Windows Terminal < 1.25) deliver Alt+Shift+C as
15
+ * the legacy ESC+'C' form (shift folded into case), which parseKey returns
16
+ * undefined for — so the binding must match the raw bytes too. Only
17
+ * copyInput needs this today.
18
+ */
19
+ readonly raw?: readonly string[];
9
20
  }
10
21
 
11
22
  /**
@@ -36,6 +47,8 @@ export const KEYBINDINGS = {
36
47
  copySelection: { keys: ["ctrl+c", "ctrl+shift+c"], hint: "C+c copy/clear" },
37
48
  /** Copy the last side-chat assistant message (pi `app.message.copy` parity). */
38
49
  copyLastMessage: { keys: ["ctrl+x"], hint: "C+x last" },
50
+ /** Copy all input editor text (expanded paste markers — submit semantics). */
51
+ copyInput: { keys: ["alt+shift+c"], raw: ["\x1bC", "\x1b[27;3;99~"], hint: "A+⇧C all" },
39
52
  /** Paste clipboard text (pi `app.clipboard.pasteImage` parity). */
40
53
  paste: { keys: ["ctrl+v", "alt+v"], hint: "C+v paste" },
41
54
  } as const satisfies Record<string, Keybinding>;
@@ -44,3 +57,9 @@ export const KEYBINDINGS = {
44
57
  export function matchesAnyKey(data: string, keys: readonly KeyId[]): boolean {
45
58
  return keys.some((key) => matchesKey(data, key));
46
59
  }
60
+
61
+ /** True when `data` matches a binding's keys or one of its raw encodings. */
62
+ export function matchesKeybinding(data: string, kb: Keybinding): boolean {
63
+ if (kb.raw?.includes(data)) return true;
64
+ return matchesAnyKey(data, kb.keys);
65
+ }
@@ -62,7 +62,7 @@ import {
62
62
  modelKey,
63
63
  type ModelChoice,
64
64
  } from "./model-switch.ts";
65
- import { KEYBINDINGS, matchesAnyKey } from "./shortcuts.ts";
65
+ import { KEYBINDINGS, matchesAnyKey, matchesKeybinding } from "./shortcuts.ts";
66
66
  import { wrapToolsWithOverlapDetection } from "./tool-wrapper.ts";
67
67
  import type { SideChatFeatures } from "./config.ts";
68
68
  import type { RetryPolicy } from "./retry.ts";
@@ -134,6 +134,8 @@ const PASTE_EMPTY_STATUS = "Clipboard is empty";
134
134
  const PASTE_STATUS_CLEAR_MS = 1200;
135
135
  /** Ctrl+X hint when the side chat has produced no assistant reply yet. */
136
136
  const NO_ASSISTANT_MESSAGE_STATUS = "No assistant message to copy yet";
137
+ /** Alt+Shift+C hint when the input editor is empty (issue #23). */
138
+ const INPUT_EMPTY_STATUS = "Input is empty";
137
139
 
138
140
  /**
139
141
  * Shared-prefix layout (#9, reverses decision #6): the main lane's system
@@ -908,6 +910,21 @@ export class SideChatOverlay implements Component, Focusable {
908
910
  this.openModelPicker();
909
911
  return;
910
912
  }
913
+ if (matchesKeybinding(data, KEYBINDINGS.copyInput)) {
914
+ // Copy the whole input editor text (issue #23): expanded paste
915
+ // markers — exactly what a submit would send. Read-only, unlike
916
+ // Ctrl+C's clear lane it never touches the draft; an empty input
917
+ // flashes a hint instead of copying. `matchesKeybinding` also accepts
918
+ // the raw legacy ESC+C form that terminals without the kitty protocol
919
+ // (Windows Terminal < 1.25) deliver for Alt+Shift+C.
920
+ const text = this.editor.getExpandedText();
921
+ if (!text) {
922
+ this.status.flash(INPUT_EMPTY_STATUS, COPIED_STATUS_CLEAR_MS);
923
+ return;
924
+ }
925
+ void this.copyTextWithFeedback(text, "input");
926
+ return;
927
+ }
911
928
  if (matchesAnyKey(data, KEYBINDINGS.copySelection.keys)) {
912
929
  // Hotkey copy: with an active mouse selection, Ctrl+C / Ctrl+Shift+C
913
930
  // copies it. Without one, Ctrl+C clears the input box (pi `app.clear`
@@ -1218,7 +1235,7 @@ export function buildSideChatHintLines(options: {
1218
1235
  const rightClickHint = features.rightClickCopyPaste
1219
1236
  ? " · R-click copy/paste"
1220
1237
  : "";
1221
- const primary = `${scrollHint} · ${KEYBINDINGS.copySelection.hint} · ${KEYBINDINGS.copyLastMessage.hint} · ${KEYBINDINGS.paste.hint}${rightClickHint} · ${modeHint} · ${escHint} · Enter send`;
1238
+ const primary = `${scrollHint} · ${KEYBINDINGS.copySelection.hint} · ${KEYBINDINGS.copyLastMessage.hint} · ${KEYBINDINGS.copyInput.hint} · ${KEYBINDINGS.paste.hint}${rightClickHint} · ${modeHint} · ${escHint} · Enter send`;
1222
1239
  const secondary = `${ALT_ACTIONS_BASE}${features.modelSwitch ? ` · ${KEYBINDINGS.modelPicker.hint}` : ""}`;
1223
1240
  return [primary, secondary];
1224
1241
  }