@fadhelhaidar/pi-ask-user 1.0.3 → 1.1.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 +4 -2
- package/dist/chunks/{index-x7fb685n.js → index-n7wxhdmh.js} +77 -1
- package/dist/chunks/index-pen3t92h.js +7 -0
- package/dist/chunks/{questionnaire-session-gdq5zqtt.js → questionnaire-session-m053x4ka.js} +238 -190
- package/dist/events.d.ts +78 -0
- package/dist/events.js +6 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +570 -74
- package/dist/locales/de.json +26 -29
- package/dist/locales/en.json +31 -27
- package/dist/locales/es.json +26 -29
- package/dist/locales/fr.json +26 -29
- package/dist/locales/pt-BR.json +26 -29
- package/dist/locales/pt.json +26 -29
- package/dist/locales/ru.json +26 -29
- package/dist/locales/uk.json +26 -29
- package/dist/locales/zh.json +28 -31
- package/docs/configuration.md +14 -0
- package/docs/hosts.md +48 -4
- package/docs/keyboard.md +33 -13
- package/docs/localization.md +7 -2
- package/docs/tool-schema.md +38 -6
- package/package.json +3 -3
- package/dist/chunks/index-bx2tbz93.js +0 -5
package/docs/hosts.md
CHANGED
|
@@ -7,7 +7,7 @@ render at all.
|
|
|
7
7
|
|
|
8
8
|
| Environment | What the model sees | What you see |
|
|
9
9
|
| --- | --- | --- |
|
|
10
|
-
| Interactive terminal | `ask_user_question` in its tool list | The full tabbed TUI overlay |
|
|
10
|
+
| Interactive terminal | `ask_user_question` in its tool list | The full tabbed TUI dialog, inline in Pi's editor area (or as an overlay — see below) |
|
|
11
11
|
| RPC / ACP host (VS Code pendant, Zed, Paseo) | `ask_user_question` in its tool list | A sequence of the host's own native select and input dialogs |
|
|
12
12
|
| Non-interactive run (no UI) | Nothing — the tool is removed | Nothing |
|
|
13
13
|
|
|
@@ -47,15 +47,58 @@ TUI produces. Trade-offs inherent to the native primitives:
|
|
|
47
47
|
Submit tab — are terminal-only; the host's native `select` and `input` primitives carry
|
|
48
48
|
no note field.
|
|
49
49
|
- Multi-select is a free-text input: type the option numbers, comma-separated
|
|
50
|
-
(`1,3`)
|
|
51
|
-
|
|
52
|
-
selection, matching `Next` with nothing toggled.
|
|
50
|
+
(`1,3`), optionally followed by `; your own answer` to submit checks and custom text
|
|
51
|
+
together. A reply that is no option number is taken as the typed custom answer; an empty
|
|
52
|
+
input commits an empty selection, matching `Next` with nothing toggled. A blank custom
|
|
53
|
+
answer stays blank (this fork keeps that behavior; upstream returns to the picker).
|
|
53
54
|
- Dismissing any dialog cancels the whole questionnaire, mirroring `Esc` in the TUI.
|
|
54
55
|
|
|
55
56
|
If the host can render neither custom UI nor dialogs, the call returns
|
|
56
57
|
`error: "no_custom_ui"` with text telling the model the user never saw the questions and
|
|
57
58
|
that it should ask them as plain chat text instead — explicitly not a decline.
|
|
58
59
|
|
|
60
|
+
## Where the dialog renders
|
|
61
|
+
|
|
62
|
+
In a terminal the questionnaire mounts inline in Pi's editor slot by default, capped at
|
|
63
|
+
70% of the terminal height (at least 12 rows), so `PageUp`/`PageDown` and the mouse wheel
|
|
64
|
+
keep scrolling the transcript while a question is open. It falls back to an overlay in two
|
|
65
|
+
cases, both of which mean the inline slot cannot receive keys:
|
|
66
|
+
|
|
67
|
+
- Another questionnaire already holds the slot.
|
|
68
|
+
- An overlay that captures input (for example `/btw`) is on screen. A question is stacked
|
|
69
|
+
on top of it as an overlay instead, and collapses to its one-line row rather than
|
|
70
|
+
hiding, because Pi pops overlays from the top of its stack.
|
|
71
|
+
|
|
72
|
+
`ctx.ui.custom` options that already allocate the dialog's rows (marked with the host
|
|
73
|
+
symbol `rpiv-ask-user-question:host-allocated-rows`) are exempt from the 70% cap, so a
|
|
74
|
+
host that sizes the slot itself is never shrunk twice.
|
|
75
|
+
|
|
76
|
+
Because Pi closes an overlay by popping the top of its stack, an answer that arrives from
|
|
77
|
+
outside (`rpiv:ask-user:answer`) removes a stacked questionnaire through its own overlay
|
|
78
|
+
handle rather than the host's close callback. Answering the upper one is unaffected; the
|
|
79
|
+
lower one leaves the stack without disturbing it.
|
|
80
|
+
|
|
81
|
+
## Answering from outside the terminal
|
|
82
|
+
|
|
83
|
+
A program driving Pi — a pane supervisor, a phone or web bridge, a test harness — can
|
|
84
|
+
answer an open questionnaire without synthesizing keystrokes:
|
|
85
|
+
|
|
86
|
+
| Event | Direction | Purpose |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `rpiv:ask-user:prompt` | out | Questions, plus the `toolCallId` naming this questionnaire |
|
|
89
|
+
| `rpiv:ask-user:blocked` | out | `{ active: true }` while input is awaited, `{ active: false }` when it ends, both carrying `toolCallId` |
|
|
90
|
+
| `rpiv:ask-user:answer` | **in** | Answer or cancel the questionnaire named by `toolCallId` |
|
|
91
|
+
| `rpiv:ask-user:answer-result` | out | One verdict per attempt: `{ ok }`, or `{ ok: false, code, reason }` with `code` one of `not_found`, `invalid_payload`, `invalid_answer`, `unsupported_host` |
|
|
92
|
+
|
|
93
|
+
`rpiv:ask-user:answer` takes `answers` — one `{ questionIndex, optionIndexes?, text?, notes? }`
|
|
94
|
+
entry per question, where single-select takes one index or `text` and multi-select takes
|
|
95
|
+
either or both — plus an optional `globalNote`, or `{ cancel: true }` to decline as `Esc`
|
|
96
|
+
does. Validation is strict and all-or-nothing: a missing, duplicated, out-of-range or
|
|
97
|
+
blank answer applies nothing and leaves the dialog open, since a partly applied answer
|
|
98
|
+
would reach the model indistinguishable from one the user gave. The first answer wins,
|
|
99
|
+
whether it came from the keyboard or from this event, and the events are terminal-only:
|
|
100
|
+
RPC hosts answer through their own dialogs and return `unsupported_host`.
|
|
101
|
+
|
|
59
102
|
## Conditional surfaces
|
|
60
103
|
|
|
61
104
|
Some parts of the dialog exist only under the right conditions:
|
|
@@ -68,6 +111,7 @@ Some parts of the dialog exist only under the right conditions:
|
|
|
68
111
|
| Side-by-side preview | An option carries a `preview`, and terminal and pane are both ≥ 100 columns |
|
|
69
112
|
| Preview pane at all | Single-select questions only |
|
|
70
113
|
| Collapse shortcut | `collapseKey` is not `"off"` |
|
|
114
|
+
| Overlay instead of inline | Another questionnaire holds the editor slot, or an overlay that captures keys is on screen |
|
|
71
115
|
| Full overlay hide on collapse | The host also exposes raw terminal input (the only path that can reopen a hidden overlay); without it, collapsing shrinks the dialog to a visible one-line row instead |
|
|
72
116
|
| Localized chrome | `@juicesharp/rpiv-i18n` is installed |
|
|
73
117
|
|
package/docs/keyboard.md
CHANGED
|
@@ -13,7 +13,8 @@ adapts to the size of your terminal.
|
|
|
13
13
|
| `Esc` | Cancel the whole questionnaire. | Everywhere except the notes editor, where it closes notes |
|
|
14
14
|
| `Tab` / `Shift+Tab` | Next / previous tab, wrapping. `→` / `←` do the same. | Multi-question dialogs only |
|
|
15
15
|
| `Space` | Insert a normal space while typing; never toggle a checkbox. | Text inputs only |
|
|
16
|
-
| `n` | Open the notes editor for the focused question — or, on the Submit tab, the global note for the whole questionnaire. | Every question tab; the Submit tab in multi-question dialogs |
|
|
16
|
+
| `n` | Open the notes editor for the focused question — or, on the Submit tab, the global note for the whole questionnaire. Configurable via `notesKey`. | Every question tab; the Submit tab in multi-question dialogs |
|
|
17
|
+
| `1`–`9` | Act on the row with that number: answer a single-select question, toggle a multi-select row, or move to `Type something.` so you can type. A digit you bound yourself, or set as `notesKey`, keeps that meaning. | Option list |
|
|
17
18
|
| `Ctrl+G` | Open Pi's configured external editor with the current custom-answer draft. | `Type something.` input |
|
|
18
19
|
| `Ctrl+U` | Clear the current custom-answer draft. | `Type something.` input |
|
|
19
20
|
| `Ctrl+]` | Collapse or expand the dialog. Configurable via `collapseKey`. | Everywhere, including while collapsed |
|
|
@@ -61,11 +62,17 @@ against the canonical English strings.
|
|
|
61
62
|
## Notes
|
|
62
63
|
|
|
63
64
|
`n` opens a notes editor on any question tab, whether the question is single- or
|
|
64
|
-
multi-select and whether or not its options carry previews.
|
|
65
|
+
multi-select and whether or not its options carry previews. On non-Latin layouts set
|
|
66
|
+
`notesKey` (see [Configuration](configuration.md)) to the character your keyboard types,
|
|
67
|
+
or to a key spec such as `"ctrl+n"`. Notes are stored in a
|
|
65
68
|
side-band keyed by tab index, not inside the answer, so writing a note does not mark a
|
|
66
69
|
question as answered — the Submit tab still lists it as outstanding. The note merges into
|
|
67
70
|
the answer when you confirm it, and reaches the model as `user notes: <text>`.
|
|
68
71
|
|
|
72
|
+
A saved note stays visible after its editor closes, as one dimmed
|
|
73
|
+
`Note for this answer: …` row that `n` reopens; the preview hint switches to
|
|
74
|
+
`Note saved: press n to edit`, and the Submit-tab review uses the same label.
|
|
75
|
+
|
|
69
76
|
On the Submit tab, `n` opens the global note editor instead — one note covering the whole
|
|
70
77
|
questionnaire. It lives outside every answer, so it survives tab switches and never marks
|
|
71
78
|
a question as answered; it reaches the model as `global note: <text>`, and submitting with
|
|
@@ -76,15 +83,19 @@ keystrokes edit the buffer, so `n` types an `n`. Pasted line breaks are preserve
|
|
|
76
83
|
|
|
77
84
|
## Collapse mode
|
|
78
85
|
|
|
79
|
-
`Ctrl+]` gets the dialog out of the way:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
with
|
|
83
|
-
press — that message names your configured key.
|
|
86
|
+
`Ctrl+]` gets the dialog out of the way: it shrinks to a single dim hint row, so the
|
|
87
|
+
transcript becomes readable and chat scrolling resumes. Press the same key to bring the
|
|
88
|
+
questionnaire back with your answers intact. The first time you collapse, Pi notifies you
|
|
89
|
+
with the key to press — that message names your configured key.
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
How it collapses depends on where the dialog opened. Inline (Pi's editor area) it simply
|
|
92
|
+
keeps the editor slot and the one-line row. An overlay is marked hidden in Pi's overlay
|
|
93
|
+
stack instead, and because Pi routes no input to a hidden overlay the collapse key is
|
|
94
|
+
additionally captured at the raw terminal level; it only acts when the questionnaire is
|
|
95
|
+
hidden or focused, so a different overlay on top of it (for example `/btw`) keeps its
|
|
96
|
+
keystrokes. A question that opened on top of `/btw` keeps its one-line row rather than
|
|
97
|
+
hiding, since Pi closes overlays from the top of the stack and would otherwise pop `/btw`
|
|
98
|
+
instead of the hidden question.
|
|
88
99
|
|
|
89
100
|
While collapsed, every keystroke other than cancel is ignored, so you cannot mutate
|
|
90
101
|
answers you cannot see.
|
|
@@ -95,15 +106,24 @@ among others — set a different `collapseKey`, or `"off"` to disable the shortc
|
|
|
95
106
|
|
|
96
107
|
## Layout
|
|
97
108
|
|
|
109
|
+
The dialog opens inline in Pi's editor area below the transcript, where Pi's own prompts
|
|
110
|
+
appear, and takes at most 70% of the terminal height (never fewer than 12 rows); it
|
|
111
|
+
scrolls within that space, so the transcript above it stays scrollable while a question is
|
|
112
|
+
open. A second questionnaire opened while an inline one is showing, or a question arriving
|
|
113
|
+
while another overlay such as `/btw` is open, opens as an overlay on top instead, so the
|
|
114
|
+
question on screen is the one receiving keys.
|
|
115
|
+
|
|
98
116
|
Options render in a vertical list. When any option in a single-select question carries a
|
|
99
117
|
`preview`, the dialog splits into a side-by-side layout with the option list on the left
|
|
100
118
|
and a bordered monospace preview box on the right — but only when both the terminal and
|
|
101
119
|
the dialog pane are at least 100 columns wide. Below that, the preview stacks underneath
|
|
102
120
|
the options instead.
|
|
103
121
|
|
|
104
|
-
When the dialog is taller than the
|
|
105
|
-
a sticky footer, and an overflow indicator shows which
|
|
106
|
-
content above, `↓` for content below, `↕` for both.
|
|
122
|
+
When the dialog is taller than the space it has, the header and question stay pinned while
|
|
123
|
+
the body scrolls between them and a sticky footer, and an overflow indicator shows which
|
|
124
|
+
direction is clipped: `↑` for content above, `↓` for content below, `↕` for both. The
|
|
125
|
+
markers never replace the focused row, padding is trimmed before content, and only the
|
|
126
|
+
padding — never the body — is dropped to make the dialog fit.
|
|
107
127
|
|
|
108
128
|
The footer hint line adapts to context — it drops the notes hint and appends the
|
|
109
129
|
`Shift+Enter` newline hint whenever a text editor has the keyboard, with `Ctrl+U` still at
|
package/docs/localization.md
CHANGED
|
@@ -57,8 +57,13 @@ the footer hint segments (including the collapsed-state line and the Submit tab'
|
|
|
57
57
|
submit-readiness prompt, incomplete-answers warning, and committed-note `Note` label, the
|
|
58
58
|
preview pane's empty and notes-affordance lines, the notes header and the global-note
|
|
59
59
|
header on the Submit tab, the
|
|
60
|
-
external-editor failure notification,
|
|
61
|
-
|
|
60
|
+
external-editor failure notification, the saved-note row label and the preview's "press
|
|
61
|
+
<key> to edit" line, and the two RPC dialog prompts. Thirty-one keys in total, all under
|
|
62
|
+
the namespace `@juicesharp/rpiv-ask-user-question`.
|
|
63
|
+
|
|
64
|
+
The transcript receipts the model's answer is rendered into — the `✓`, `✗`, `not
|
|
65
|
+
answered` and `no answer` strings — are translated too (`transcript.*`), because they are
|
|
66
|
+
chrome in the transcript rather than prompt input.
|
|
62
67
|
|
|
63
68
|
Everything the *model* reads stays English by design: the tool description, the parameter
|
|
64
69
|
schema descriptions, error messages, and the reserved-label list. Those are prompt inputs,
|
package/docs/tool-schema.md
CHANGED
|
@@ -100,27 +100,59 @@ question is blank.
|
|
|
100
100
|
|
|
101
101
|
Cancelling, and any result with neither answer segments nor a global note, both collapse
|
|
102
102
|
to the single string `User declined to answer questions` so the model sees one canonical
|
|
103
|
-
signal. Partial submission is allowed: unanswered questions
|
|
103
|
+
signal. Partial submission is allowed: unanswered questions contribute no segment, and
|
|
104
|
+
their question text is listed in an `unanswered: "<question>", …` segment after the
|
|
105
|
+
answers, with the same indexes in `details.unanswered`. A global note counts as an answer
|
|
106
|
+
on its own, so a note with every question blank still yields the answered envelope.
|
|
104
107
|
A cancelled result always reads as the decline in text; its note, if any, survives only
|
|
105
108
|
in `details.globalNote`.
|
|
106
109
|
|
|
107
110
|
## Event contract
|
|
108
111
|
|
|
109
|
-
The package publishes
|
|
110
|
-
|
|
112
|
+
The package publishes three events on Pi's event bus — one before the dialog is shown, two
|
|
113
|
+
bracketing the wait — and accepts a fourth that answers the questionnaire from outside the
|
|
114
|
+
terminal. Import them from the `/events` subpath:
|
|
111
115
|
|
|
112
116
|
```ts
|
|
113
|
-
import { ASK_USER_PROMPT_EVENT, type AskUserPromptEventPayload } from "@
|
|
117
|
+
import { ASK_USER_PROMPT_EVENT, type AskUserPromptEventPayload } from "@fadhelhaidar/pi-ask-user/events";
|
|
114
118
|
|
|
115
119
|
pi.events.on(ASK_USER_PROMPT_EVENT, (payload: AskUserPromptEventPayload) => {
|
|
116
|
-
// payload.questions[].{ question, header, multiSelect, options[] }
|
|
120
|
+
// payload.toolCallId, payload.questions[].{ question, header, multiSelect, options[] }
|
|
117
121
|
// payload.questions[].options[].{ label, description, hasPreview }
|
|
118
122
|
});
|
|
119
123
|
```
|
|
120
124
|
|
|
121
125
|
The channel name is `rpiv:ask-user:prompt`. Preview *content* is deliberately not shipped
|
|
122
126
|
in the payload — only `hasPreview: boolean` — so listeners forwarding the event across a
|
|
123
|
-
process or network boundary stay cheap.
|
|
127
|
+
process or network boundary stay cheap. The prompt event fires after validation passes and
|
|
128
|
+
before the dialog is shown; `rpiv:ask-user:blocked` then brackets the wait with
|
|
129
|
+
`{ active: true, toolCallId }` and `{ active: false, toolCallId }`, including when the
|
|
130
|
+
dialog never mounts because an answer arrived first.
|
|
131
|
+
|
|
132
|
+
## Answering from outside the terminal
|
|
133
|
+
|
|
134
|
+
A supervisor, bridge or test harness emits `rpiv:ask-user:answer` on the same bus:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
pi.events.emit(ASK_USER_ANSWER_EVENT, {
|
|
138
|
+
toolCallId,
|
|
139
|
+
answers: [{ questionIndex: 0, optionIndexes: [1], notes: "keep the bus factor up" }],
|
|
140
|
+
globalNote: "ship on Tuesday",
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`answers` carries one entry per question — `optionIndexes` for chosen options (authored
|
|
145
|
+
order is preserved, not the order given here), `text` for a typed custom answer, `notes`
|
|
146
|
+
for that question's note; multi-select accepts both `optionIndexes` and `text` together,
|
|
147
|
+
single-select takes one index or `text`. Send `{ cancel: true }` instead to decline as `Esc`
|
|
148
|
+
does. Every attempt gets exactly one `rpiv:ask-user:answer-result` verdict:
|
|
149
|
+
`{ ok: true }`, or `{ ok: false, code, reason }` with `code` one of `not_found`,
|
|
150
|
+
`invalid_payload`, `invalid_answer` or `unsupported_host`. Validation is strict and
|
|
151
|
+
all-or-nothing — a blank, duplicated or out-of-range answer applies nothing and leaves the
|
|
152
|
+
dialog open — and the first answer wins, whether it came from the keyboard or this event.
|
|
153
|
+
Questionnaires are addressed by the `toolCallId` on the prompt event, so parallel calls
|
|
154
|
+
are independent. Events answer terminal dialogs only; RPC hosts answer through their own
|
|
155
|
+
dialogs and reject with `unsupported_host`.
|
|
124
156
|
|
|
125
157
|
Stability policy for the `rpiv:*` namespace: channel names are immutable, payload changes
|
|
126
158
|
are append-only and always optional, payloads stay JSON-safe, and any breaking change ships
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fadhelhaidar/pi-ask-user",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Standalone Pi questionnaire with combined multi-select and custom answers, Enter-only toggling, and preserved drafts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@earendil-works/pi-coding-agent": "1.0
|
|
61
|
-
"@earendil-works/pi-tui": "1.0
|
|
60
|
+
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
61
|
+
"@earendil-works/pi-tui": "1.2.0",
|
|
62
62
|
"@juicesharp/rpiv-i18n": "2.12.0",
|
|
63
63
|
"typescript": "5.9.3",
|
|
64
64
|
"vitest": "4.1.10"
|