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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/index.ts +49 -2
  3. 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(displayMode: AskDisplayMode) {
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
- const customResult = await ctx.ui.custom<AskUIResult | null>(customFactory, buildCustomUIOptions(effectiveDisplayMode));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ask-user",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Interactive ask_user tool for pi-coding-agent with searchable split-pane selection UI, multi-select, and freeform input",
5
5
  "type": "module",
6
6
  "keywords": [