@xynogen/pix-ask 0.2.2 → 0.2.5
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 +1 -1
- package/package.json +3 -3
- package/src/once.ts +2 -1
- package/src/questionnaire.ts +8 -4
- package/src/rpc.ts +7 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Pi tool — structured questionnaire UI (`ask_user`).
|
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
Registers the `ask_user` tool in Pi. When the agent needs to resolve ambiguous requirements, it presents up to 4 structured multiple-choice questions in a TUI dialog. Each question requires 2–4 options with labels and descriptions; supports multi-select and markdown side-by-side previews for richer context. Single-select questions auto-append a "Type something." row for free-form input
|
|
7
|
+
Registers the `ask_user` tool in Pi. When the agent needs to resolve ambiguous requirements, it presents up to 4 structured multiple-choice questions in a TUI dialog. Each question requires 2–4 options with labels and descriptions; supports multi-select and markdown side-by-side previews for richer context. Single-select questions (without a preview) auto-append a "Type something." row for free-form input; multi-select questions append a "Next" row to advance. Single-select questions with a `preview` skip the free-form row to make room for the side-by-side layout. In non-interactive (RPC/JSON) mode, falls back to text-based prompts. The agent uses this tool before proceeding rather than guessing at intent.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-ask",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Pi tool
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "Pi tool — structured questionnaire UI (ask_user)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"typebox": "^1.1.38",
|
|
38
|
-
"@xynogen/pix-pretty": "
|
|
38
|
+
"@xynogen/pix-pretty": "^1.7.9"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/once.ts
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export function once(pi: object, key: string, fn: () => void): void {
|
|
16
16
|
const g = globalThis as { __pixOnce?: WeakMap<object, Set<string>> };
|
|
17
|
-
|
|
17
|
+
if (!g.__pixOnce) g.__pixOnce = new WeakMap<object, Set<string>>();
|
|
18
|
+
const registry = g.__pixOnce;
|
|
18
19
|
let loaded = registry.get(pi);
|
|
19
20
|
if (!loaded) {
|
|
20
21
|
loaded = new Set<string>();
|
package/src/questionnaire.ts
CHANGED
|
@@ -65,7 +65,9 @@ export class AskQuestionnaire extends Container {
|
|
|
65
65
|
// ── Accessors ──────────────────────────────────────────────────────
|
|
66
66
|
|
|
67
67
|
private get currentQ(): QuestionData {
|
|
68
|
-
|
|
68
|
+
const q = this.params.questions[this.currentIndex];
|
|
69
|
+
if (!q) throw new Error("currentIndex out of bounds");
|
|
70
|
+
return q;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
private get filteredOptions(): OptionData[] {
|
|
@@ -209,7 +211,7 @@ export class AskQuestionnaire extends Container {
|
|
|
209
211
|
const q = this.currentQ;
|
|
210
212
|
if (prev.kind === "multi") {
|
|
211
213
|
for (let i = 0; i < q.options.length; i++) {
|
|
212
|
-
if (prev.selected?.includes(q.options[i]
|
|
214
|
+
if (prev.selected?.includes(q.options[i]?.label ?? "")) {
|
|
213
215
|
this.multiChecked.add(i);
|
|
214
216
|
}
|
|
215
217
|
}
|
|
@@ -354,7 +356,8 @@ export class AskQuestionnaire extends Container {
|
|
|
354
356
|
this.selectedOptionIndex = Math.min(idx, this.totalItems - 1);
|
|
355
357
|
this.refresh();
|
|
356
358
|
} else {
|
|
357
|
-
const opt = this.filteredOptions[idx]
|
|
359
|
+
const opt = this.filteredOptions[idx];
|
|
360
|
+
if (!opt) return;
|
|
358
361
|
this.recordAnswer("option", opt.label, undefined, opt.preview);
|
|
359
362
|
this.nextQuestion();
|
|
360
363
|
}
|
|
@@ -434,7 +437,8 @@ export class AskQuestionnaire extends Container {
|
|
|
434
437
|
const pad = " ".repeat(LABEL_COL);
|
|
435
438
|
|
|
436
439
|
for (let i = start; i < end; i++) {
|
|
437
|
-
const item = items[i]
|
|
440
|
+
const item = items[i];
|
|
441
|
+
if (!item) continue;
|
|
438
442
|
const sel = i === this.selectedOptionIndex;
|
|
439
443
|
const ptr = sel ? t.fg("accent", "→") : " ";
|
|
440
444
|
|
package/src/rpc.ts
CHANGED
|
@@ -6,14 +6,18 @@ import type { QuestionAnswer, QuestionnaireResult } from "./types.js";
|
|
|
6
6
|
// Used when ctx.hasUI is false (headless / JSON / print mode).
|
|
7
7
|
|
|
8
8
|
export async function rpcFallback(
|
|
9
|
-
ui: {
|
|
9
|
+
ui: {
|
|
10
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
11
|
+
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
12
|
+
},
|
|
10
13
|
params: Params,
|
|
11
14
|
): Promise<QuestionnaireResult> {
|
|
12
15
|
const answers: QuestionAnswer[] = [];
|
|
13
16
|
let cancelled = false;
|
|
14
17
|
|
|
15
18
|
for (let i = 0; i < params.questions.length; i++) {
|
|
16
|
-
const q = params.questions[i]
|
|
19
|
+
const q = params.questions[i];
|
|
20
|
+
if (!q) break;
|
|
17
21
|
const header = q.header;
|
|
18
22
|
|
|
19
23
|
if (q.multiSelect) {
|
|
@@ -69,7 +73,7 @@ export async function rpcFallback(
|
|
|
69
73
|
const opt = q.options.find(
|
|
70
74
|
(o) =>
|
|
71
75
|
chosen === o.label || `${o.label} — ${o.description}` === chosen,
|
|
72
|
-
)
|
|
76
|
+
);
|
|
73
77
|
answers.push({
|
|
74
78
|
questionIndex: i,
|
|
75
79
|
question: q.question,
|