pi-btw 0.2.0 → 0.2.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 +8 -0
- package/extensions/btw.ts +58 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ A small [pi](https://github.com/badlogic/pi-mono) extension that adds a `/btw` s
|
|
|
13
13
|
- keeps a continuous BTW thread by default
|
|
14
14
|
- supports `/btw:tangent` for a contextless side thread that does not inherit the current main-session conversation
|
|
15
15
|
- opens a focused BTW modal shell with its own composer and transcript
|
|
16
|
+
- keeps the BTW overlay open while you switch focus back to the main editor with `Alt+/`
|
|
16
17
|
- keeps BTW thread entries out of the main agent's future context
|
|
17
18
|
- lets you inject the full thread, or a summary of it, back into the main agent
|
|
18
19
|
- optionally saves an individual BTW exchange as a visible session note with `--save`
|
|
@@ -69,6 +70,13 @@ pi install /absolute/path/to/pi-btw
|
|
|
69
70
|
- persists the BTW exchange as hidden thread state
|
|
70
71
|
- with `--save`, also saves that single exchange as a visible session note
|
|
71
72
|
|
|
73
|
+
## Overlay controls
|
|
74
|
+
|
|
75
|
+
- `Alt+/` toggles focus between BTW and the main editor without closing the overlay
|
|
76
|
+
- `Ctrl+Alt+W` is a fallback focus toggle for terminals that do not deliver `Alt+/` as a usable shortcut
|
|
77
|
+
- `Esc` still dismisses BTW immediately while the overlay is focused
|
|
78
|
+
- BTW now opens top-centered so the main session remains visible underneath it
|
|
79
|
+
|
|
72
80
|
### `/btw:new [question]`
|
|
73
81
|
|
|
74
82
|
- clears the current BTW thread
|
package/extensions/btw.ts
CHANGED
|
@@ -31,6 +31,11 @@ import {
|
|
|
31
31
|
const BTW_MESSAGE_TYPE = "btw-note";
|
|
32
32
|
const BTW_ENTRY_TYPE = "btw-thread-entry";
|
|
33
33
|
const BTW_RESET_TYPE = "btw-thread-reset";
|
|
34
|
+
const BTW_FOCUS_SHORTCUTS = [Key.alt("/"), Key.ctrlAlt("w")] as const;
|
|
35
|
+
|
|
36
|
+
function matchesBtwFocusShortcut(data: string): boolean {
|
|
37
|
+
return BTW_FOCUS_SHORTCUTS.some((shortcut) => matchesKey(data, shortcut));
|
|
38
|
+
}
|
|
34
39
|
|
|
35
40
|
const BTW_SYSTEM_PROMPT = [
|
|
36
41
|
"You are having an aside conversation with the user, separate from their main working session.",
|
|
@@ -940,6 +945,7 @@ class BtwOverlayComponent extends Container implements Focusable {
|
|
|
940
945
|
private readonly getMode: () => BtwThreadMode;
|
|
941
946
|
private readonly onSubmitCallback: (value: string) => void;
|
|
942
947
|
private readonly onDismissCallback: () => void;
|
|
948
|
+
private readonly onUnfocusCallback: () => void;
|
|
943
949
|
private readonly tui: TUI;
|
|
944
950
|
private readonly theme: ExtensionContext["ui"]["theme"];
|
|
945
951
|
private transcriptLines: string[] = [];
|
|
@@ -966,6 +972,7 @@ class BtwOverlayComponent extends Container implements Focusable {
|
|
|
966
972
|
getMode: () => BtwThreadMode,
|
|
967
973
|
onSubmit: (value: string) => void,
|
|
968
974
|
onDismiss: () => void,
|
|
975
|
+
onUnfocus: () => void,
|
|
969
976
|
) {
|
|
970
977
|
super();
|
|
971
978
|
this.tui = tui;
|
|
@@ -975,6 +982,7 @@ class BtwOverlayComponent extends Container implements Focusable {
|
|
|
975
982
|
this.getMode = getMode;
|
|
976
983
|
this.onSubmitCallback = onSubmit;
|
|
977
984
|
this.onDismissCallback = onDismiss;
|
|
985
|
+
this.onUnfocusCallback = onUnfocus;
|
|
978
986
|
|
|
979
987
|
this.modeText = new Text("", 1, 0);
|
|
980
988
|
this.summaryText = new Text("", 1, 0);
|
|
@@ -1034,10 +1042,15 @@ class BtwOverlayComponent extends Container implements Focusable {
|
|
|
1034
1042
|
|
|
1035
1043
|
private getDialogHeight(): number {
|
|
1036
1044
|
const terminalRows = process.stdout.rows ?? 30;
|
|
1037
|
-
return Math.max(
|
|
1045
|
+
return Math.max(18, Math.min(32, Math.floor(terminalRows * 0.78)));
|
|
1038
1046
|
}
|
|
1039
1047
|
|
|
1040
1048
|
handleInput(data: string): void {
|
|
1049
|
+
if (matchesBtwFocusShortcut(data)) {
|
|
1050
|
+
this.onUnfocusCallback();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1041
1054
|
if (matchesKey(data, Key.pageUp)) {
|
|
1042
1055
|
this.followTranscript = false;
|
|
1043
1056
|
this.transcriptScrollOffset = Math.max(0, this.transcriptScrollOffset - Math.max(1, this.transcriptViewportHeight - 1));
|
|
@@ -1151,7 +1164,7 @@ class BtwOverlayComponent extends Container implements Focusable {
|
|
|
1151
1164
|
|
|
1152
1165
|
const status = this.getStatus() ?? "Ready. Enter submits; Escape dismisses without clearing.";
|
|
1153
1166
|
this.statusText.setText(status);
|
|
1154
|
-
this.hintsText.setText("Enter submit · Escape dismiss · PgUp/PgDn scroll
|
|
1167
|
+
this.hintsText.setText("Enter submit · Alt+/ toggle focus · Escape dismiss · PgUp/PgDn scroll");
|
|
1155
1168
|
this.tui.requestRender();
|
|
1156
1169
|
}
|
|
1157
1170
|
}
|
|
@@ -1189,6 +1202,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
1189
1202
|
overlayRuntime = null;
|
|
1190
1203
|
}
|
|
1191
1204
|
|
|
1205
|
+
function toggleOverlayFocus(): void {
|
|
1206
|
+
const handle = overlayRuntime?.handle;
|
|
1207
|
+
if (!handle) {
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
handle.setHidden(false);
|
|
1212
|
+
if (handle.isFocused()) {
|
|
1213
|
+
handle.unfocus();
|
|
1214
|
+
} else {
|
|
1215
|
+
handle.focus();
|
|
1216
|
+
}
|
|
1217
|
+
overlayRuntime?.refresh?.();
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function focusOverlay(): void {
|
|
1221
|
+
const handle = overlayRuntime?.handle;
|
|
1222
|
+
if (!handle) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
handle.setHidden(false);
|
|
1227
|
+
handle.focus();
|
|
1228
|
+
overlayRuntime?.refresh?.();
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1192
1231
|
function removeBtwSessionSubscription(sessionRuntime: BtwSessionRuntime, unsubscribe: () => void): void {
|
|
1193
1232
|
if (!sessionRuntime.subscriptions.delete(unsubscribe)) {
|
|
1194
1233
|
return;
|
|
@@ -1318,9 +1357,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1318
1357
|
|
|
1319
1358
|
if (overlayRuntime?.handle) {
|
|
1320
1359
|
subscribeOverlayToActiveBtwSession(ctx);
|
|
1321
|
-
|
|
1322
|
-
overlayRuntime.handle.focus();
|
|
1323
|
-
overlayRuntime.refresh?.();
|
|
1360
|
+
focusOverlay();
|
|
1324
1361
|
return;
|
|
1325
1362
|
}
|
|
1326
1363
|
|
|
@@ -1363,6 +1400,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1363
1400
|
() => {
|
|
1364
1401
|
void dismissOverlaySession();
|
|
1365
1402
|
},
|
|
1403
|
+
() => {
|
|
1404
|
+
overlayRuntime?.handle?.unfocus();
|
|
1405
|
+
overlayRuntime?.refresh?.();
|
|
1406
|
+
},
|
|
1366
1407
|
);
|
|
1367
1408
|
|
|
1368
1409
|
overlay.focused = runtime.handle?.isFocused() ?? true;
|
|
@@ -1393,8 +1434,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1393
1434
|
width: "78%",
|
|
1394
1435
|
minWidth: 72,
|
|
1395
1436
|
maxHeight: "78%",
|
|
1396
|
-
anchor: "center",
|
|
1397
|
-
margin: 1,
|
|
1437
|
+
anchor: "top-center",
|
|
1438
|
+
margin: { top: 1, left: 2, right: 2 },
|
|
1439
|
+
nonCapturing: true,
|
|
1398
1440
|
},
|
|
1399
1441
|
onHandle: (handle) => {
|
|
1400
1442
|
runtime.handle = handle;
|
|
@@ -1836,6 +1878,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
1836
1878
|
dismissOverlay();
|
|
1837
1879
|
});
|
|
1838
1880
|
|
|
1881
|
+
for (const shortcut of BTW_FOCUS_SHORTCUTS) {
|
|
1882
|
+
pi.registerShortcut(shortcut, {
|
|
1883
|
+
description: "Toggle BTW overlay focus while leaving it open.",
|
|
1884
|
+
handler: async (_args, _ctx) => {
|
|
1885
|
+
toggleOverlayFocus();
|
|
1886
|
+
},
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1839
1890
|
pi.registerCommand("btw", {
|
|
1840
1891
|
description: "Continue a side conversation in a focused BTW modal. Add --save to also persist a visible note.",
|
|
1841
1892
|
handler: async (args, ctx) => {
|