pi-ask-user 0.7.0 → 0.8.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 +15 -0
- package/index.ts +49 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ High-quality video: [ask-user-demo.mp4](./media/ask-user-demo.mp4)
|
|
|
17
17
|
- User-toggleable extra context on structured selections
|
|
18
18
|
- Context display support
|
|
19
19
|
- Configurable display mode: `overlay` (modal, default) or `inline` (rendered directly in the flow)
|
|
20
|
+
- Runtime overlay toggle: press `alt+o` while the prompt is open to temporarily hide/show the popup so you can read prior agent output, then press `alt+o` again to bring it back
|
|
20
21
|
- Pi-TUI-aligned keybinding and editor behavior
|
|
21
22
|
- Custom TUI rendering for tool calls and results
|
|
22
23
|
- System prompt integration via `promptSnippet` and `promptGuidelines`
|
|
@@ -102,6 +103,20 @@ Effective behavior order:
|
|
|
102
103
|
|
|
103
104
|
Unrecognised values are silently ignored and fall back to `"overlay"`.
|
|
104
105
|
|
|
106
|
+
## Controls
|
|
107
|
+
|
|
108
|
+
While an `ask_user` prompt is open:
|
|
109
|
+
|
|
110
|
+
| Key | Action |
|
|
111
|
+
|-----|--------|
|
|
112
|
+
| `alt+o` | Hide/show the overlay popup so you can read the agent's prior output. Available in `overlay` mode only. The first time you hide it, a notification reminds you that `alt+o` brings it back. |
|
|
113
|
+
| `ctrl+g` | Toggle the optional comment/extra-context row (when `allowComment: true`). |
|
|
114
|
+
| `enter` | Confirm the focused option, submit a freeform response, or submit/skip an optional comment. |
|
|
115
|
+
| `esc` | Clear the search filter, exit freeform/comment mode, or cancel the prompt. |
|
|
116
|
+
| `↑` / `↓` | Navigate options. |
|
|
117
|
+
|
|
118
|
+
If you prefer never to see the overlay, set `displayMode: "inline"` per call or `PI_ASK_USER_DISPLAY_MODE=inline` globally.
|
|
119
|
+
|
|
105
120
|
## Result details
|
|
106
121
|
|
|
107
122
|
All tool results include a structured `details` object for rendering and session state reconstruction:
|
package/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
Markdown,
|
|
22
22
|
type MarkdownTheme,
|
|
23
23
|
matchesKey,
|
|
24
|
+
type OverlayHandle,
|
|
24
25
|
Spacer,
|
|
25
26
|
Text,
|
|
26
27
|
type TUI,
|
|
@@ -248,6 +249,10 @@ function isCommentToggleKey(data: string): boolean {
|
|
|
248
249
|
return matchesKey(data, Key.ctrl("g"));
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
function isOverlayHideToggleKey(data: string): boolean {
|
|
253
|
+
return matchesKey(data, Key.alt("o"));
|
|
254
|
+
}
|
|
255
|
+
|
|
251
256
|
type AskMode = "select" | "freeform" | "comment";
|
|
252
257
|
|
|
253
258
|
const ASK_OVERLAY_MAX_HEIGHT_RATIO = 0.85;
|
|
@@ -259,8 +264,12 @@ const SINGLE_SELECT_SPLIT_PANE_RIGHT_MIN_WIDTH = 28;
|
|
|
259
264
|
const SINGLE_SELECT_SPLIT_PANE_SEPARATOR = " │ ";
|
|
260
265
|
const FREEFORM_SENTINEL = "\u270f\ufe0f Type custom response...";
|
|
261
266
|
const COMMENT_TOGGLE_LABEL = "Add extra context after selection";
|
|
267
|
+
const OVERLAY_HIDE_TOGGLE_LABEL = "alt+o";
|
|
262
268
|
|
|
263
|
-
function buildCustomUIOptions(
|
|
269
|
+
function buildCustomUIOptions(
|
|
270
|
+
displayMode: AskDisplayMode,
|
|
271
|
+
onHandle?: (handle: OverlayHandle) => void,
|
|
272
|
+
) {
|
|
264
273
|
switch (displayMode) {
|
|
265
274
|
case "inline":
|
|
266
275
|
return undefined;
|
|
@@ -274,6 +283,7 @@ function buildCustomUIOptions(displayMode: AskDisplayMode) {
|
|
|
274
283
|
maxHeight: "85%",
|
|
275
284
|
margin: 1,
|
|
276
285
|
},
|
|
286
|
+
...(onHandle ? { onHandle } : {}),
|
|
277
287
|
};
|
|
278
288
|
default: {
|
|
279
289
|
const _exhaustive: never = displayMode;
|
|
@@ -287,6 +297,7 @@ function buildCustomUIOptions(displayMode: AskDisplayMode) {
|
|
|
287
297
|
maxHeight: "85%",
|
|
288
298
|
margin: 1,
|
|
289
299
|
},
|
|
300
|
+
...(onHandle ? { onHandle } : {}),
|
|
290
301
|
};
|
|
291
302
|
}
|
|
292
303
|
}
|
|
@@ -868,6 +879,7 @@ class AskComponent extends Container {
|
|
|
868
879
|
private allowMultiple: boolean;
|
|
869
880
|
private allowFreeform: boolean;
|
|
870
881
|
private allowComment: boolean;
|
|
882
|
+
private displayMode: AskDisplayMode;
|
|
871
883
|
private tui: TUI;
|
|
872
884
|
private theme: Theme;
|
|
873
885
|
private keybindings: KeybindingsManager;
|
|
@@ -909,6 +921,7 @@ class AskComponent extends Container {
|
|
|
909
921
|
allowMultiple: boolean,
|
|
910
922
|
allowFreeform: boolean,
|
|
911
923
|
allowComment: boolean,
|
|
924
|
+
displayMode: AskDisplayMode,
|
|
912
925
|
tui: TUI,
|
|
913
926
|
theme: Theme,
|
|
914
927
|
keybindings: KeybindingsManager,
|
|
@@ -922,6 +935,7 @@ class AskComponent extends Container {
|
|
|
922
935
|
this.allowMultiple = allowMultiple;
|
|
923
936
|
this.allowFreeform = allowFreeform;
|
|
924
937
|
this.allowComment = allowComment;
|
|
938
|
+
this.displayMode = displayMode;
|
|
925
939
|
this.tui = tui;
|
|
926
940
|
this.theme = theme;
|
|
927
941
|
this.keybindings = keybindings;
|
|
@@ -1044,6 +1058,9 @@ class AskComponent extends Container {
|
|
|
1044
1058
|
|
|
1045
1059
|
private updateHelpText(): void {
|
|
1046
1060
|
const theme = this.theme;
|
|
1061
|
+
const overlayHint = this.displayMode === "overlay"
|
|
1062
|
+
? literalHint(theme, OVERLAY_HIDE_TOGGLE_LABEL, "hide")
|
|
1063
|
+
: null;
|
|
1047
1064
|
if (this.mode === "freeform" || this.mode === "comment") {
|
|
1048
1065
|
const alternateCancelKeys = this.keybindings
|
|
1049
1066
|
.getKeys("tui.select.cancel")
|
|
@@ -1052,6 +1069,7 @@ class AskComponent extends Container {
|
|
|
1052
1069
|
keybindingHint(theme, this.keybindings, "tui.input.submit", this.mode === "comment" ? "submit/skip" : "submit"),
|
|
1053
1070
|
keybindingHint(theme, this.keybindings, "tui.input.newLine", "newline"),
|
|
1054
1071
|
literalHint(theme, "esc", "back"),
|
|
1072
|
+
overlayHint,
|
|
1055
1073
|
alternateCancelKeys.length > 0 ? literalHint(theme, formatKeyList(alternateCancelKeys), "cancel") : null,
|
|
1056
1074
|
]
|
|
1057
1075
|
.filter((hint): hint is string => !!hint)
|
|
@@ -1065,6 +1083,7 @@ class AskComponent extends Container {
|
|
|
1065
1083
|
literalHint(theme, "↑↓", "navigate"),
|
|
1066
1084
|
literalHint(theme, "space", "toggle"),
|
|
1067
1085
|
this.allowComment ? literalHint(theme, "ctrl+g", "toggle context") : null,
|
|
1086
|
+
overlayHint,
|
|
1068
1087
|
keybindingHint(theme, this.keybindings, "tui.select.confirm", "submit"),
|
|
1069
1088
|
keybindingHint(theme, this.keybindings, "tui.select.cancel", "cancel"),
|
|
1070
1089
|
]
|
|
@@ -1080,6 +1099,7 @@ class AskComponent extends Container {
|
|
|
1080
1099
|
keybindingHint(theme, this.keybindings, "tui.editor.deleteCharBackward", "erase"),
|
|
1081
1100
|
literalHint(theme, "↑↓", "navigate"),
|
|
1082
1101
|
this.allowComment ? literalHint(theme, "ctrl+g", "toggle context") : null,
|
|
1102
|
+
overlayHint,
|
|
1083
1103
|
keybindingHint(theme, this.keybindings, "tui.select.confirm", "select"),
|
|
1084
1104
|
literalHint(theme, "esc", "clear/cancel"),
|
|
1085
1105
|
alternateCancelKeys.length > 0
|
|
@@ -1461,6 +1481,9 @@ export default function(pi: ExtensionAPI) {
|
|
|
1461
1481
|
});
|
|
1462
1482
|
|
|
1463
1483
|
let result: AskUIResult | null;
|
|
1484
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
1485
|
+
let removeOverlayInputListener: (() => void) | undefined;
|
|
1486
|
+
let hasAnnouncedHide = false;
|
|
1464
1487
|
try {
|
|
1465
1488
|
const customFactory = (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (result: AskUIResult | null) => void) => {
|
|
1466
1489
|
if (signal) {
|
|
@@ -1479,6 +1502,7 @@ export default function(pi: ExtensionAPI) {
|
|
|
1479
1502
|
allowMultiple,
|
|
1480
1503
|
allowFreeform,
|
|
1481
1504
|
allowComment,
|
|
1505
|
+
effectiveDisplayMode,
|
|
1482
1506
|
tui,
|
|
1483
1507
|
theme,
|
|
1484
1508
|
keybindings,
|
|
@@ -1486,7 +1510,28 @@ export default function(pi: ExtensionAPI) {
|
|
|
1486
1510
|
);
|
|
1487
1511
|
};
|
|
1488
1512
|
|
|
1489
|
-
|
|
1513
|
+
// Register a raw terminal input listener for alt+o so the overlay can be
|
|
1514
|
+
// toggled even while it is hidden (hidden overlays do not receive input).
|
|
1515
|
+
// Inline mode does not need this because the prompt is already non-modal.
|
|
1516
|
+
if (effectiveDisplayMode === "overlay" && typeof ctx.ui.onTerminalInput === "function") {
|
|
1517
|
+
removeOverlayInputListener = ctx.ui.onTerminalInput((data) => {
|
|
1518
|
+
if (!isOverlayHideToggleKey(data) || !overlayHandle) return undefined;
|
|
1519
|
+
const nextHidden = !overlayHandle.isHidden();
|
|
1520
|
+
overlayHandle.setHidden(nextHidden);
|
|
1521
|
+
if (nextHidden && !hasAnnouncedHide) {
|
|
1522
|
+
hasAnnouncedHide = true;
|
|
1523
|
+
ctx.ui.notify?.(`ask_user hidden — press ${OVERLAY_HIDE_TOGGLE_LABEL} to reopen`, "info");
|
|
1524
|
+
}
|
|
1525
|
+
return { consume: true };
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const customResult = await ctx.ui.custom<AskUIResult | null>(
|
|
1530
|
+
customFactory,
|
|
1531
|
+
buildCustomUIOptions(effectiveDisplayMode, (handle) => {
|
|
1532
|
+
overlayHandle = handle;
|
|
1533
|
+
}),
|
|
1534
|
+
);
|
|
1490
1535
|
|
|
1491
1536
|
if (customResult !== undefined) {
|
|
1492
1537
|
result = customResult;
|
|
@@ -1502,6 +1547,8 @@ export default function(pi: ExtensionAPI) {
|
|
|
1502
1547
|
isError: true,
|
|
1503
1548
|
details: { error: message },
|
|
1504
1549
|
};
|
|
1550
|
+
} finally {
|
|
1551
|
+
removeOverlayInputListener?.();
|
|
1505
1552
|
}
|
|
1506
1553
|
|
|
1507
1554
|
if (result === null) {
|
package/package.json
CHANGED