@narumitw/pi-btw 0.55.3 → 0.56.0
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 +125 -88
- package/dist/index.ts +140 -45
- package/dist/index.ts.map +2 -2
- package/package.json +6 -9
- package/src/btw.ts +31 -26
- package/src/fullscreen-ui.ts +106 -17
- package/src/menu.ts +34 -2
- package/src/settings.ts +16 -0
package/README.md
CHANGED
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@narumitw/pi-btw) [](https://pi.dev) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
Ask
|
|
6
|
-
|
|
7
|
-
Bring back only the answer or context you explicitly choose.
|
|
5
|
+
Ask questions in a temporary side thread without adding them to the main Pi conversation.
|
|
6
|
+
Only context you explicitly bring back is loaded into the main editor.
|
|
8
7
|
|
|
9
8
|
## ✨ Features
|
|
10
9
|
|
|
11
|
-
- Starts a
|
|
12
|
-
- Uses any persisted main-session branch as context without switching
|
|
13
|
-
- Supports scrollable answers, follow-up questions, queued steering, and
|
|
10
|
+
- Starts a side thread immediately with `/btw <question>` or opens the manager with `/btw`.
|
|
11
|
+
- Uses any persisted main-session branch as context without switching branches.
|
|
12
|
+
- Supports scrollable answers, transcript search, follow-up questions, queued steering, and in-memory resume.
|
|
14
13
|
- Keeps side questions and answers out of the main conversation by default.
|
|
15
|
-
- Brings back
|
|
16
|
-
- Uses Pi's current model and thinking level or
|
|
14
|
+
- Brings back the latest answer, a question suffix, an exact range, or the complete thread only when requested.
|
|
15
|
+
- Uses Pi's current model and thinking level or saved pi-btw choices.
|
|
17
16
|
|
|
18
17
|
## 📦 Install
|
|
19
18
|
|
|
@@ -35,15 +34,17 @@ pi -e ./packages/pi-btw
|
|
|
35
34
|
```
|
|
36
35
|
|
|
37
36
|
The package declares `dist/index.ts`, so an unbuilt local checkout must run the build before Pi loads the package directory.
|
|
37
|
+
Pi extensions run with the Pi process's user permissions, so install only trusted packages.
|
|
38
38
|
|
|
39
39
|
## 🚀 Quick start
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
In TUI mode, run `/btw <question>` to start immediately or `/btw` to choose context and settings first.
|
|
42
|
+
The side thread stays separate until you explicitly bring context to the main editor.
|
|
43
43
|
|
|
44
44
|
## 💬 Commands
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
`/btw` is TUI-only.
|
|
47
|
+
Open the manager or provide the first question immediately:
|
|
47
48
|
|
|
48
49
|
```text
|
|
49
50
|
/btw
|
|
@@ -59,72 +60,97 @@ Examples:
|
|
|
59
60
|
/btw is this API name idiomatic?
|
|
60
61
|
```
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
The
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
63
|
+
### Choose context or resume a thread
|
|
64
|
+
|
|
65
|
+
Running `/btw` opens a manager with **Start side thread** selected first.
|
|
66
|
+
**Start from main thread tree…** opens Pi's session tree and uses the root-to-selected-entry path, including the selected entry, as context.
|
|
67
|
+
This choice preserves the main editor draft and does not navigate, fork, append to, or switch the main conversation.
|
|
68
|
+
The tree is a snapshot of persisted entries, and the side thread keeps immutable context even if the main conversation later changes.
|
|
69
|
+
Escape returns to the manager, and Ctrl+C closes the flow.
|
|
70
|
+
Native tree copying reports success or failure.
|
|
71
|
+
An explicit `Shift+L` label edit is the only main-session mutation available from this selector and persists through Pi.
|
|
72
|
+
|
|
73
|
+
When non-empty threads exist in memory, **Resume side thread** opens a bounded searchable list.
|
|
74
|
+
Search matches the first question and question count while retaining each raw thread ID.
|
|
75
|
+
Each row uses the first question as its fixed title and shows the question count.
|
|
76
|
+
Rows are ordered by the newest recorded answer or visible error; opening and closing without a new result does not reorder them.
|
|
77
|
+
**Settings** controls the starting thinking level, fixed-level shortcut memory, and automatic selection copying.
|
|
78
|
+
`/btw <question>` bypasses the manager and always starts a new thread.
|
|
79
|
+
|
|
80
|
+
### Use the side-thread workspace
|
|
81
|
+
|
|
82
|
+
The side thread uses a dedicated full-screen terminal view with answers above the editor.
|
|
83
|
+
The main agent can continue running, but main-screen rendering stays suspended until `/btw` closes so new output cannot move a mouse selection.
|
|
84
|
+
Returning to Pi redraws everything produced while the main view was hidden.
|
|
85
|
+
|
|
86
|
+
A fixed `btw · side thread` header identifies the workspace while scrolling.
|
|
87
|
+
Messages use Pi's normal user and assistant presentation without turn numbers or role labels.
|
|
88
|
+
Type a question and press Enter for each turn.
|
|
89
|
+
Previous successful questions and answers remain visible and available to the side model.
|
|
90
|
+
|
|
91
|
+
Drag the primary mouse button across transcript text to select it.
|
|
92
|
+
Automatic selection copying is on by default and immediately requests a copy through Pi's host clipboard helper.
|
|
93
|
+
When **Copy selection automatically** is off, the selection stays highlighted and Pi's effective `app.message.copy` binding copies it.
|
|
94
|
+
Manual mode requires Pi's current fullscreen selection APIs.
|
|
95
|
+
If those APIs are unavailable, pi-btw restores the main TUI and asks you to update Pi or re-enable automatic copying.
|
|
96
|
+
The view reports `No selection to copy` when that binding is used without an active selection.
|
|
97
|
+
It reports `Copied!` when Pi accepts a clipboard request and `Copy failed` when Pi rejects it.
|
|
98
|
+
Actual clipboard access still depends on the operating system and terminal.
|
|
99
|
+
Ctrl+C always cancels the side flow, even when `app.message.copy` is also mapped to Ctrl+C.
|
|
100
|
+
|
|
101
|
+
Press `Ctrl+Shift+F` to search completed or in-progress transcript content.
|
|
102
|
+
Press Enter or `Ctrl+G` for the next match, `Shift+Enter` or `Ctrl+Shift+G` for the previous match, and Escape to close search.
|
|
103
|
+
Search uses Pi's active theme, excludes fixed header and footer text, and returns focus to the composer when closed.
|
|
104
|
+
|
|
105
|
+
### Change thinking level or queue steering
|
|
106
|
+
|
|
107
|
+
The header shows the side thread's current thinking level.
|
|
108
|
+
In the composer, use Pi's configured `app.thinking.cycle` shortcut (`Shift+Tab` by default) to cycle levels supported by the side model.
|
|
109
|
+
Later questions use the displayed level until it changes again.
|
|
110
|
+
For a fixed starting level, shortcut changes are saved to `pi-btw.json` by default.
|
|
111
|
+
Turn **Remember thinking level changes** off to keep them local to the thread.
|
|
112
|
+
With **Same as main thread**, shortcut changes are always local.
|
|
113
|
+
Neither setting changes the main session's thinking level.
|
|
114
|
+
|
|
115
|
+
During a response, the transcript and composer remain visible above `Answering…`.
|
|
116
|
+
Submit another question to queue it as `Steering`.
|
|
117
|
+
Queued questions appear in submission order and run one at a time after the active response.
|
|
118
|
+
Each queued question uses the side thread's thinking level when its turn starts.
|
|
119
|
+
A failed response remains visible and does not discard later queued questions.
|
|
120
|
+
Steering never appends to the main conversation or editor.
|
|
121
|
+
|
|
122
|
+
Use the mouse wheel, trackpad, or `PgUp`/`PgDn` to scroll transcript history.
|
|
123
|
+
The footer shows history keys only when scrolling is available.
|
|
124
|
+
Ctrl+C cancels the active response and discards the current draft and steering queue.
|
|
125
|
+
Completed questions, answers, and visible errors remain available through Resume until the extension instance ends.
|
|
126
|
+
|
|
127
|
+
### Bring context to the main editor
|
|
128
|
+
|
|
129
|
+
After a successful answer, press `Ctrl+R` to choose context for the main editor.
|
|
130
|
+
The scope menu shows the size of the latest question and answer and the full thread.
|
|
131
|
+
Choose the latest question and answer, everything from one question onward, an exact range, or the full thread.
|
|
132
|
+
Question-suffix, exact-range, and full-thread choices preview the editable context block before the side thread closes.
|
|
133
|
+
Escape returns, while Ctrl+C closes without bringing context back.
|
|
134
|
+
|
|
135
|
+
The exact-range selector supports whole-line and editor-style character selection.
|
|
136
|
+
It reports selected line, message, and approximate token counts.
|
|
137
|
+
Press Space to select the current raw source line, use Up or Down to extend by lines, and press Space again to clear.
|
|
138
|
+
Alternatively, move with arrow keys and extend a character selection with Shift plus an arrow key.
|
|
139
|
+
Starting a Shift selection replaces an active line selection.
|
|
140
|
+
Selected lines show a `●` marker as well as highlighting.
|
|
141
|
+
Pi's configured keys control vertical navigation, bringing, and going back, with Up, Down, Enter, and Escape as defaults.
|
|
142
|
+
Selection follows raw source text rather than terminal-wrapped rows.
|
|
143
|
+
|
|
144
|
+
Bringing context closes the side thread and loads a deterministic, editable block into Pi's main editor without sending it.
|
|
145
|
+
If a draft already exists, append is the recommended default.
|
|
146
|
+
Replace is marked destructive and requires a second confirmation.
|
|
147
|
+
Cancel returns to the side thread without changing either draft, and concurrent editor updates are preserved.
|
|
148
|
+
A success message reports whether context was loaded, appended, or replaced and gives its approximate size.
|
|
149
|
+
|
|
150
|
+
Without an explicit bring action, closing `/btw` never changes the main conversation.
|
|
151
|
+
Non-empty threads remain in memory only for Resume during the current extension instance.
|
|
152
|
+
`/new`, Pi `/resume`, `/reload`, extension replacement, and process restart discard retained threads.
|
|
153
|
+
Unsent drafts, steering queues, interrupted answers, and model credentials are not retained.
|
|
128
154
|
|
|
129
155
|
## ⚙️ Settings
|
|
130
156
|
|
|
@@ -142,14 +168,15 @@ The normal location is `~/.pi/agent/pi-btw.json`.
|
|
|
142
168
|
{
|
|
143
169
|
"model": "anthropic/claude-sonnet-4-5",
|
|
144
170
|
"thinkingLevel": "low",
|
|
145
|
-
"rememberThinkingLevelChanges": true
|
|
171
|
+
"rememberThinkingLevelChanges": true,
|
|
172
|
+
"fullscreenCopyOnSelect": true
|
|
146
173
|
}
|
|
147
174
|
```
|
|
148
175
|
|
|
149
176
|
The `model` value uses `provider/model-id` format.
|
|
150
177
|
Only the first `/` is the separator, so model IDs may contain additional slashes, such as `openrouter/anthropic/claude-sonnet`.
|
|
151
178
|
The configured model must exist in Pi's model registry and have usable credentials.
|
|
152
|
-
If it
|
|
179
|
+
If it is missing or unauthenticated, pi-btw warns and falls back to the current session model.
|
|
153
180
|
If neither model is available, `/btw` reports an error and stops.
|
|
154
181
|
This selection affects only `/btw`; it does not change the main session model.
|
|
155
182
|
|
|
@@ -170,17 +197,25 @@ When **Same as main thread** is selected, shortcut changes stay local even when
|
|
|
170
197
|
If a shortcut write fails, the local change remains active and pi-btw warns that it was not remembered.
|
|
171
198
|
A failed Settings-screen save instead restores the previous displayed value.
|
|
172
199
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
200
|
+
`fullscreenCopyOnSelect` controls only pi-btw's dedicated fullscreen view and defaults to `true` when omitted.
|
|
201
|
+
Turn **Copy selection automatically** off to retain highlighted selections and copy them with Pi's effective `app.message.copy` binding.
|
|
202
|
+
Pi-btw does not inherit Pi core's setting of the same name because Pi's public extension API does not expose its effective value.
|
|
203
|
+
|
|
204
|
+
Reading a missing settings file has no side effects.
|
|
205
|
+
Pi-btw creates it only after a Settings change or a remembered shortcut change.
|
|
206
|
+
Within one Pi process, saves run in order and publish atomically through a same-directory temporary file and rename.
|
|
207
|
+
Saves preserve `model` and unknown fields.
|
|
208
|
+
Malformed or invalid files block saves and remain unchanged.
|
|
209
|
+
Files must be valid UTF-8 and no larger than 64 KiB.
|
|
210
|
+
Separate Pi processes and external editors are outside the in-process ordering boundary.
|
|
211
|
+
The file is read for every `/btw` invocation, so edits apply without `/reload`.
|
|
179
212
|
|
|
180
|
-
##
|
|
213
|
+
## 🚧 Limitations
|
|
181
214
|
|
|
182
|
-
|
|
183
|
-
|
|
215
|
+
- `/btw` supports TUI mode only.
|
|
216
|
+
- Resume state is memory-only and lasts only for the current extension instance.
|
|
217
|
+
- A side thread retains the latest 40,000 characters of main-conversation context and adds a truncation notice when earlier content is omitted.
|
|
218
|
+
- Clipboard access depends on Pi's host helper, the operating system, and the terminal.
|
|
184
219
|
|
|
185
220
|
## 🗂️ Package layout
|
|
186
221
|
|
|
@@ -193,12 +228,14 @@ packages/pi-btw/
|
|
|
193
228
|
│ ├── index.ts
|
|
194
229
|
│ ├── btw.ts
|
|
195
230
|
│ ├── bring-to-main.ts
|
|
231
|
+
│ ├── fullscreen-ui.ts
|
|
196
232
|
│ ├── main-tree-picker.ts
|
|
197
233
|
│ ├── menu.ts
|
|
198
234
|
│ ├── settings.ts
|
|
199
235
|
│ ├── side-thread.ts
|
|
200
236
|
│ ├── text.ts
|
|
201
237
|
│ └── transcript-pager.ts
|
|
238
|
+
├── test/
|
|
202
239
|
├── README.md
|
|
203
240
|
├── LICENSE
|
|
204
241
|
├── tsconfig.json
|
package/dist/index.ts
CHANGED
|
@@ -503,10 +503,13 @@ var FullscreenUiDisposedError = class extends Error {
|
|
|
503
503
|
this.name = "FullscreenUiDisposedError";
|
|
504
504
|
}
|
|
505
505
|
};
|
|
506
|
-
async function runBtwFullscreen(ctx, run, dependencies = {}) {
|
|
507
|
-
const createTui = dependencies.createTui ?? ((parent, theme) => createBtwFullscreenTui(
|
|
506
|
+
async function runBtwFullscreen(ctx, run, options = {}, dependencies = {}) {
|
|
507
|
+
const createTui = dependencies.createTui ?? ((parent, theme, keybindings, fullscreenOptions) => createBtwFullscreenTui(
|
|
508
508
|
parent,
|
|
509
509
|
theme,
|
|
510
|
+
keybindings,
|
|
511
|
+
fullscreenOptions.copyOnSelect ?? true,
|
|
512
|
+
dependencies.manualSelectionCopySupported ?? hasManualSelectionCopyApi(),
|
|
510
513
|
dependencies.openUrl ?? openUrlInBrowser,
|
|
511
514
|
dependencies.copyToClipboard ?? copyToHostClipboard
|
|
512
515
|
));
|
|
@@ -529,7 +532,8 @@ async function runBtwFullscreen(ctx, run, dependencies = {}) {
|
|
|
529
532
|
}
|
|
530
533
|
done(value);
|
|
531
534
|
},
|
|
532
|
-
createTui
|
|
535
|
+
createTui,
|
|
536
|
+
options
|
|
533
537
|
);
|
|
534
538
|
return host;
|
|
535
539
|
},
|
|
@@ -550,7 +554,7 @@ async function runBtwFullscreen(ctx, run, dependencies = {}) {
|
|
|
550
554
|
var btwInputListeners = /* @__PURE__ */ new WeakMap();
|
|
551
555
|
function dispatchBtwInput(listeners, data) {
|
|
552
556
|
let current = data;
|
|
553
|
-
for (const group of [listeners.beforeViewport, listeners.regular]) {
|
|
557
|
+
for (const group of [listeners.beforeAll, listeners.beforeViewport, listeners.regular]) {
|
|
554
558
|
for (const listener of group) {
|
|
555
559
|
const result = listener(current);
|
|
556
560
|
if (result?.consume) return result;
|
|
@@ -560,10 +564,14 @@ function dispatchBtwInput(listeners, data) {
|
|
|
560
564
|
return current === data ? void 0 : { data: current };
|
|
561
565
|
}
|
|
562
566
|
var BtwTuiAltScreen = class extends TuiAltScreen {
|
|
567
|
+
hasFocusedOverlay() {
|
|
568
|
+
return this.isOverlayFocused();
|
|
569
|
+
}
|
|
563
570
|
addInputListener(listener) {
|
|
564
571
|
let listeners = btwInputListeners.get(this);
|
|
565
572
|
if (!listeners) {
|
|
566
573
|
const registeredListeners = {
|
|
574
|
+
beforeAll: /* @__PURE__ */ new Set(),
|
|
567
575
|
beforeViewport: /* @__PURE__ */ new Set(),
|
|
568
576
|
regular: /* @__PURE__ */ new Set()
|
|
569
577
|
};
|
|
@@ -574,6 +582,12 @@ var BtwTuiAltScreen = class extends TuiAltScreen {
|
|
|
574
582
|
listeners.regular.add(listener);
|
|
575
583
|
return () => listeners.regular.delete(listener);
|
|
576
584
|
}
|
|
585
|
+
addInputListenerBeforeAll(listener) {
|
|
586
|
+
const listeners = btwInputListeners.get(this);
|
|
587
|
+
if (!listeners) return super.addInputListener(listener);
|
|
588
|
+
listeners.beforeAll.add(listener);
|
|
589
|
+
return () => listeners.beforeAll.delete(listener);
|
|
590
|
+
}
|
|
577
591
|
addInputListenerBeforeViewport(listener) {
|
|
578
592
|
const listeners = btwInputListeners.get(this);
|
|
579
593
|
if (!listeners) return super.addInputListener(listener);
|
|
@@ -586,26 +600,64 @@ var BtwTuiAltScreen = class extends TuiAltScreen {
|
|
|
586
600
|
super.removeInputListener(listener);
|
|
587
601
|
return;
|
|
588
602
|
}
|
|
603
|
+
listeners.beforeAll.delete(listener);
|
|
589
604
|
listeners.beforeViewport.delete(listener);
|
|
590
605
|
listeners.regular.delete(listener);
|
|
591
606
|
}
|
|
592
607
|
};
|
|
593
|
-
|
|
608
|
+
var BRACKETED_PASTE_START = "\x1B[200~";
|
|
609
|
+
var BRACKETED_PASTE_END = "\x1B[201~";
|
|
610
|
+
function hasManualSelectionCopyApi() {
|
|
611
|
+
return typeof TuiAltScreen.prototype.hasActiveSelection === "function" && typeof TuiAltScreen.prototype.copyActiveSelectionToClipboard === "function";
|
|
612
|
+
}
|
|
613
|
+
function createBtwFullscreenTui(parent, theme, keybindings, copyOnSelect, manualSelectionCopySupported, openUrl, copyToClipboard2) {
|
|
614
|
+
if (!copyOnSelect && !manualSelectionCopySupported) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
"Manual fullscreen selection copying is unavailable in this Pi version; update Pi or enable automatic selection copying."
|
|
617
|
+
);
|
|
618
|
+
}
|
|
594
619
|
const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
620
|
+
const fullscreen = new BtwTuiAltScreen(
|
|
621
|
+
parent.terminal,
|
|
622
|
+
parent.getShowHardwareCursor(),
|
|
623
|
+
void 0,
|
|
624
|
+
{
|
|
625
|
+
mouse: true,
|
|
626
|
+
copyOnSelect,
|
|
627
|
+
searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
|
|
628
|
+
searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
|
|
629
|
+
openUrl,
|
|
630
|
+
copySelection: async (text) => {
|
|
631
|
+
try {
|
|
632
|
+
await copyToClipboard2(text);
|
|
633
|
+
return true;
|
|
634
|
+
} catch {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
606
637
|
}
|
|
607
638
|
}
|
|
608
|
-
|
|
639
|
+
);
|
|
640
|
+
if (!copyOnSelect) {
|
|
641
|
+
let isInBracketedPaste = false;
|
|
642
|
+
fullscreen.addInputListenerBeforeViewport((data) => {
|
|
643
|
+
const wasInBracketedPaste = isInBracketedPaste;
|
|
644
|
+
const startsBracketedPaste = data.includes(BRACKETED_PASTE_START);
|
|
645
|
+
if (startsBracketedPaste) isInBracketedPaste = true;
|
|
646
|
+
if (isInBracketedPaste && data.includes(BRACKETED_PASTE_END)) {
|
|
647
|
+
isInBracketedPaste = false;
|
|
648
|
+
}
|
|
649
|
+
if (wasInBracketedPaste || startsBracketedPaste || fullscreen.hasFocusedOverlay() || isKeyRelease(data) || !keybindings.matches(data, "app.message.copy")) {
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
if (!fullscreen.hasActiveSelection()) {
|
|
653
|
+
fullscreen.flash("No selection to copy");
|
|
654
|
+
return { consume: true };
|
|
655
|
+
}
|
|
656
|
+
void fullscreen.copyActiveSelectionToClipboard().catch(() => fullscreen.flash("Copy failed"));
|
|
657
|
+
return { consume: true };
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
return fullscreen;
|
|
609
661
|
}
|
|
610
662
|
function openUrlInBrowser(target) {
|
|
611
663
|
const [command, args] = process.platform === "darwin" ? ["open", [target]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", target]] : ["xdg-open", [target]];
|
|
@@ -613,7 +665,7 @@ function openUrlInBrowser(target) {
|
|
|
613
665
|
}).unref();
|
|
614
666
|
}
|
|
615
667
|
var BtwFullscreenHost = class {
|
|
616
|
-
constructor(parent, theme, keybindings, ctx, run, done, createTui) {
|
|
668
|
+
constructor(parent, theme, keybindings, ctx, run, done, createTui, options) {
|
|
617
669
|
this.parent = parent;
|
|
618
670
|
this.theme = theme;
|
|
619
671
|
this.keybindings = keybindings;
|
|
@@ -621,6 +673,7 @@ var BtwFullscreenHost = class {
|
|
|
621
673
|
this.run = run;
|
|
622
674
|
this.done = done;
|
|
623
675
|
this.createTui = createTui;
|
|
676
|
+
this.options = options;
|
|
624
677
|
queueMicrotask(() => void this.start());
|
|
625
678
|
}
|
|
626
679
|
parent;
|
|
@@ -630,6 +683,7 @@ var BtwFullscreenHost = class {
|
|
|
630
683
|
run;
|
|
631
684
|
done;
|
|
632
685
|
createTui;
|
|
686
|
+
options;
|
|
633
687
|
fullscreen;
|
|
634
688
|
parentOverlay;
|
|
635
689
|
cancelActiveCustom;
|
|
@@ -665,10 +719,10 @@ var BtwFullscreenHost = class {
|
|
|
665
719
|
this.parent.stop({ preserveScreen: true });
|
|
666
720
|
this.parentStopped = true;
|
|
667
721
|
if (this.disposed) throw new FullscreenUiDisposedError();
|
|
668
|
-
this.fullscreen = this.createTui(this.parent, this.theme);
|
|
722
|
+
this.fullscreen = this.createTui(this.parent, this.theme, this.keybindings, this.options);
|
|
669
723
|
this.fullscreenCreated = true;
|
|
670
724
|
this.fullscreen.start();
|
|
671
|
-
const addHardCancelListener = this.fullscreen.addInputListenerBeforeViewport?.bind(this.fullscreen) ?? this.fullscreen.addInputListener.bind(this.fullscreen);
|
|
725
|
+
const addHardCancelListener = this.fullscreen.addInputListenerBeforeAll?.bind(this.fullscreen) ?? this.fullscreen.addInputListenerBeforeViewport?.bind(this.fullscreen) ?? this.fullscreen.addInputListener.bind(this.fullscreen);
|
|
672
726
|
this.removeHardCancelListener = addHardCancelListener((data) => {
|
|
673
727
|
if (isKeyRelease(data) || !matchesKey2(data, Key2.ctrl("c"))) return void 0;
|
|
674
728
|
try {
|
|
@@ -1020,6 +1074,7 @@ Use the provided conversation context only as background. Answer the user's side
|
|
|
1020
1074
|
|
|
1021
1075
|
// src/settings.ts
|
|
1022
1076
|
var BTW_SETTINGS_FILE = "pi-btw.json";
|
|
1077
|
+
var DEFAULT_FULLSCREEN_COPY_ON_SELECT = true;
|
|
1023
1078
|
var DEFAULT_REMEMBER_THINKING_LEVEL_CHANGES = true;
|
|
1024
1079
|
var MAX_SETTINGS_BYTES = 64 * 1024;
|
|
1025
1080
|
var mutationQueues = /* @__PURE__ */ new Map();
|
|
@@ -1044,6 +1099,11 @@ function normalizeBtwSettings(value) {
|
|
|
1044
1099
|
if (typeof remember !== "boolean") return void 0;
|
|
1045
1100
|
settings.rememberThinkingLevelChanges = remember;
|
|
1046
1101
|
}
|
|
1102
|
+
if (Object.hasOwn(value, "fullscreenCopyOnSelect")) {
|
|
1103
|
+
const copyOnSelect = Reflect.get(value, "fullscreenCopyOnSelect");
|
|
1104
|
+
if (typeof copyOnSelect !== "boolean") return void 0;
|
|
1105
|
+
settings.fullscreenCopyOnSelect = copyOnSelect;
|
|
1106
|
+
}
|
|
1047
1107
|
return settings;
|
|
1048
1108
|
}
|
|
1049
1109
|
function parseBtwModelReference(reference) {
|
|
@@ -1052,6 +1112,9 @@ function parseBtwModelReference(reference) {
|
|
|
1052
1112
|
if (separator <= 0 || separator === reference.length - 1) return void 0;
|
|
1053
1113
|
return { provider: reference.slice(0, separator), modelId: reference.slice(separator + 1) };
|
|
1054
1114
|
}
|
|
1115
|
+
function effectiveFullscreenCopyOnSelect(settings) {
|
|
1116
|
+
return settings.fullscreenCopyOnSelect ?? DEFAULT_FULLSCREEN_COPY_ON_SELECT;
|
|
1117
|
+
}
|
|
1055
1118
|
function effectiveRememberThinkingLevelChanges(settings) {
|
|
1056
1119
|
return settings.rememberThinkingLevelChanges ?? DEFAULT_REMEMBER_THINKING_LEVEL_CHANGES;
|
|
1057
1120
|
}
|
|
@@ -1189,6 +1252,10 @@ function applyBtwSettingsPatch(current, patch) {
|
|
|
1189
1252
|
if (Object.hasOwn(patch, "rememberThinkingLevelChanges")) {
|
|
1190
1253
|
updated.rememberThinkingLevelChanges = patch.rememberThinkingLevelChanges;
|
|
1191
1254
|
}
|
|
1255
|
+
if (Object.hasOwn(patch, "fullscreenCopyOnSelect")) {
|
|
1256
|
+
if (patch.fullscreenCopyOnSelect === void 0) delete updated.fullscreenCopyOnSelect;
|
|
1257
|
+
else updated.fullscreenCopyOnSelect = patch.fullscreenCopyOnSelect;
|
|
1258
|
+
}
|
|
1192
1259
|
return updated;
|
|
1193
1260
|
}
|
|
1194
1261
|
function isSettingsDocument(value) {
|
|
@@ -1246,7 +1313,8 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1246
1313
|
kind: "actions",
|
|
1247
1314
|
title: "Pi BTW",
|
|
1248
1315
|
lines: [
|
|
1249
|
-
`Thinking: ${displayThinkingSummary(state.settings)} \xB7 Remember changes: ${displayRememberSummary(state.settings)}
|
|
1316
|
+
`Thinking: ${displayThinkingSummary(state.settings)} \xB7 Remember changes: ${displayRememberSummary(state.settings)}`,
|
|
1317
|
+
`Copy on select: ${effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off"}`
|
|
1250
1318
|
],
|
|
1251
1319
|
items: [
|
|
1252
1320
|
{
|
|
@@ -1272,7 +1340,7 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1272
1340
|
{
|
|
1273
1341
|
id: "settings",
|
|
1274
1342
|
label: "Settings",
|
|
1275
|
-
description: "Choose
|
|
1343
|
+
description: "Choose thinking, shortcut memory, and selection copying",
|
|
1276
1344
|
to: state.kind === "invalid" ? "invalid" : "settings"
|
|
1277
1345
|
}
|
|
1278
1346
|
],
|
|
@@ -1311,6 +1379,14 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1311
1379
|
currentValue: effectiveRememberThinkingLevelChanges(state.settings) ? "On" : "Off",
|
|
1312
1380
|
values: ["On", "Off"],
|
|
1313
1381
|
action: "set-remember"
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
id: "fullscreenCopyOnSelect",
|
|
1385
|
+
label: "Copy selection automatically",
|
|
1386
|
+
description: "Copy mouse selections immediately instead of with the configured copy key.",
|
|
1387
|
+
currentValue: effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off",
|
|
1388
|
+
values: ["On", "Off"],
|
|
1389
|
+
action: "set-fullscreen-copy"
|
|
1314
1390
|
}
|
|
1315
1391
|
]
|
|
1316
1392
|
}),
|
|
@@ -1368,6 +1444,21 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1368
1444
|
if (!signal.aborted) notifySaveFailure(ctx, error);
|
|
1369
1445
|
return { kind: "rejected" };
|
|
1370
1446
|
}
|
|
1447
|
+
},
|
|
1448
|
+
"set-fullscreen-copy": async ({ value, signal }) => {
|
|
1449
|
+
if (value !== "On" && value !== "Off") return { kind: "rejected" };
|
|
1450
|
+
try {
|
|
1451
|
+
await updateSettings(
|
|
1452
|
+
{ fullscreenCopyOnSelect: value === "On" },
|
|
1453
|
+
{ settingsPath, signal }
|
|
1454
|
+
);
|
|
1455
|
+
if (signal.aborted) return { kind: "rejected" };
|
|
1456
|
+
notifySafely(ctx, `Copy selection automatically: ${value}.`, "info");
|
|
1457
|
+
return { kind: "stay" };
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
if (!signal.aborted) notifySaveFailure(ctx, error);
|
|
1460
|
+
return { kind: "rejected" };
|
|
1461
|
+
}
|
|
1371
1462
|
}
|
|
1372
1463
|
}
|
|
1373
1464
|
});
|
|
@@ -2475,29 +2566,33 @@ function btw(pi, dependencies = {}) {
|
|
|
2475
2566
|
}
|
|
2476
2567
|
const startingTurnCount = state?.thread.turns.length ?? 0;
|
|
2477
2568
|
try {
|
|
2478
|
-
await runFullscreen(
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
state
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2569
|
+
await runFullscreen(
|
|
2570
|
+
ctx,
|
|
2571
|
+
(fullscreenCtx) => {
|
|
2572
|
+
if (!state) {
|
|
2573
|
+
const createdAt = Date.now();
|
|
2574
|
+
state = {
|
|
2575
|
+
id: `btw-${nextThreadNumber}`,
|
|
2576
|
+
thread: createSideThread(
|
|
2577
|
+
selectedConversationContext ?? buildConversationContext(fullscreenCtx.sessionManager.getBranch())
|
|
2578
|
+
),
|
|
2579
|
+
thinkingLevel: settings.thinkingLevel ?? pi.getThinkingLevel(),
|
|
2580
|
+
createdAt,
|
|
2581
|
+
updatedAt: createdAt
|
|
2582
|
+
};
|
|
2583
|
+
nextThreadNumber += 1;
|
|
2584
|
+
}
|
|
2585
|
+
return runThread({
|
|
2586
|
+
initialQuestion: question || void 0,
|
|
2587
|
+
selected: resolution.selected,
|
|
2588
|
+
thinkingLevel: state.thinkingLevel,
|
|
2589
|
+
rememberThinkingLevelChanges: !sameAsMainThinkingLevel && effectiveRememberThinkingLevelChanges(settings),
|
|
2590
|
+
state,
|
|
2591
|
+
ctx: fullscreenCtx
|
|
2592
|
+
});
|
|
2593
|
+
},
|
|
2594
|
+
{ copyOnSelect: effectiveFullscreenCopyOnSelect(settings) }
|
|
2595
|
+
);
|
|
2501
2596
|
} finally {
|
|
2502
2597
|
if (state?.title && state.thread.turns.length > 0) {
|
|
2503
2598
|
if (state.thread.turns.length > startingTurnCount) {
|