@xynogen/pix-ask 0.2.22 → 0.2.24
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 -2
- package/package.json +1 -1
- package/src/components.ts +1 -1
- package/src/index.ts +5 -2
- package/src/questionnaire.ts +14 -15
package/README.md
CHANGED
|
@@ -4,9 +4,15 @@ Pi tool — structured questionnaire UI (`ask_user`).
|
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
Registers the `ask_user` tool
|
|
7
|
+
Registers the `ask_user` tool — up to 4 structured multiple-choice questions in a TUI dialog when the agent needs to resolve ambiguous requirements, instead of guessing.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Each question: 2–4 options with labels + descriptions. Supports multi-select and markdown side-by-side previews.
|
|
10
|
+
- **Single-select** (no preview) → appends a "Type something." free-form row.
|
|
11
|
+
- **Single-select with `preview`** → skips the free-form row to fit the side-by-side layout.
|
|
12
|
+
- **Multi-select** → appends a "Next" row to advance.
|
|
13
|
+
- Non-interactive (RPC/JSON) mode falls back to text prompts.
|
|
14
|
+
|
|
15
|
+
**Away notifications** — while the dialog is open, `ask_user` holds the shared **agent-state** coordinator `blocked` (via `withAgentBlock` from [`@xynogen/pix-runtime`](https://www.npmjs.com/package/@xynogen/pix-runtime)), so a herdr pane pings an away user when a question waits.
|
|
10
16
|
|
|
11
17
|
## Install
|
|
12
18
|
|
package/package.json
CHANGED
package/src/components.ts
CHANGED
|
@@ -38,7 +38,7 @@ export class TabBar implements Component {
|
|
|
38
38
|
const tag = `${num}.${this.questions[i]?.header}`;
|
|
39
39
|
parts.push(active ? t.fg("accent", t.bold(tag)) : t.fg("dim", tag));
|
|
40
40
|
}
|
|
41
|
-
const line = parts.join(t.fg("
|
|
41
|
+
const line = parts.join(t.fg("muted", " "));
|
|
42
42
|
return [
|
|
43
43
|
truncateToWidth(
|
|
44
44
|
t.fg("accent", "╭─") +
|
package/src/index.ts
CHANGED
|
@@ -57,6 +57,7 @@ export default function registerAsk(pi: ExtensionAPI): void {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// SAFETY: ParamsSchema validates tool input before execute receives it.
|
|
60
61
|
const typed = params as unknown as Params;
|
|
61
62
|
|
|
62
63
|
if (!Array.isArray(typed.questions) || typed.questions.length === 0) {
|
|
@@ -113,9 +114,11 @@ export default function registerAsk(pi: ExtensionAPI): void {
|
|
|
113
114
|
const questions = Array.isArray(args.questions) ? args.questions : [];
|
|
114
115
|
const count = questions.length;
|
|
115
116
|
const firstQ = questions[0]?.question ?? "";
|
|
116
|
-
const head = `${theme.fg("toolTitle", theme.bold("ask_user"))} ${theme.fg("
|
|
117
|
+
const head = `${theme.fg("toolTitle", theme.bold("ask_user"))} ${theme.fg("dim", firstQ)}`;
|
|
117
118
|
text.setText(
|
|
118
|
-
dotJoin([head, theme.fg("
|
|
119
|
+
dotJoin([head, theme.fg("muted", pluralize(count, "question"))], (s) =>
|
|
120
|
+
theme.fg("muted", s),
|
|
121
|
+
),
|
|
119
122
|
);
|
|
120
123
|
return text;
|
|
121
124
|
},
|
package/src/questionnaire.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
terminalModalHeight,
|
|
19
19
|
} from "@xynogen/pix-pretty/modal-frame";
|
|
20
20
|
import { ChipEditor } from "./chip-editor.js";
|
|
21
|
-
import { dim } from "./components.js";
|
|
22
21
|
import { checkboxGlyphs, selectionGlyph } from "./glyphs.js";
|
|
23
22
|
import { safeMarkdownTheme, sentinelsFor } from "./helpers.js";
|
|
24
23
|
import type { OptionData, Params, QuestionData } from "./schema.js";
|
|
@@ -127,8 +126,8 @@ export class AskQuestionnaire extends Container {
|
|
|
127
126
|
selectList: {
|
|
128
127
|
selectedPrefix: (s: string) => this.theme.fg("accent", s),
|
|
129
128
|
selectedText: (s: string) => this.theme.fg("accent", s),
|
|
130
|
-
description: (s: string) => this.theme.fg("
|
|
131
|
-
scrollInfo: (s: string) => this.theme.fg("
|
|
129
|
+
description: (s: string) => this.theme.fg("dim", s),
|
|
130
|
+
scrollInfo: (s: string) => this.theme.fg("muted", s),
|
|
132
131
|
noMatch: (s: string) => this.theme.fg("warning", s),
|
|
133
132
|
},
|
|
134
133
|
},
|
|
@@ -470,19 +469,19 @@ export class AskQuestionnaire extends Container {
|
|
|
470
469
|
Math.max(10, inner - LABEL_COL),
|
|
471
470
|
);
|
|
472
471
|
for (const w of wrapped) {
|
|
473
|
-
lines.push(truncateToWidth(`${pad}${t.fg("
|
|
472
|
+
lines.push(truncateToWidth(`${pad}${t.fg("dim", w)}`, inner, ""));
|
|
474
473
|
}
|
|
475
474
|
}
|
|
476
475
|
} else if (item.kind === "other") {
|
|
477
476
|
const label = sel
|
|
478
477
|
? t.fg("accent", t.bold(SENTINEL_FREEFORM))
|
|
479
478
|
: t.fg("text", t.bold(SENTINEL_FREEFORM));
|
|
480
|
-
lines.push(truncateToWidth(`${ptr} ${t.fg("
|
|
479
|
+
lines.push(truncateToWidth(`${ptr} ${t.fg("muted", "✎")} ${label}`, inner, ""));
|
|
481
480
|
} else if (item.kind === "next") {
|
|
482
481
|
const label = sel
|
|
483
482
|
? t.fg("accent", t.bold(SENTINEL_NEXT))
|
|
484
483
|
: t.fg("text", t.bold(SENTINEL_NEXT));
|
|
485
|
-
lines.push(truncateToWidth(`${ptr} ${t.fg("
|
|
484
|
+
lines.push(truncateToWidth(`${ptr} ${t.fg("muted", "→")} ${label}`, inner, ""));
|
|
486
485
|
}
|
|
487
486
|
if (sel)
|
|
488
487
|
this.selectedOptionRows = { start: itemStart, end: Math.max(itemStart + 1, lines.length) };
|
|
@@ -494,7 +493,7 @@ export class AskQuestionnaire extends Container {
|
|
|
494
493
|
private renderPreview(width: number): string[] {
|
|
495
494
|
const item = this.selectedItem;
|
|
496
495
|
if (item?.kind !== "option" || !item.option?.preview) {
|
|
497
|
-
return [this.theme.fg("
|
|
496
|
+
return [this.theme.fg("muted", "No preview")];
|
|
498
497
|
}
|
|
499
498
|
|
|
500
499
|
const mdText = item.option.preview;
|
|
@@ -506,7 +505,7 @@ export class AskQuestionnaire extends Container {
|
|
|
506
505
|
}
|
|
507
506
|
|
|
508
507
|
const lines = wrapTextWithAnsi(mdText, mdWidth);
|
|
509
|
-
return lines.map((l) => truncateToWidth(this.theme.fg("
|
|
508
|
+
return lines.map((l) => truncateToWidth(this.theme.fg("dim", l), mdWidth, ""));
|
|
510
509
|
}
|
|
511
510
|
|
|
512
511
|
override render(termWidth: number): string[] {
|
|
@@ -528,8 +527,8 @@ export class AskQuestionnaire extends Container {
|
|
|
528
527
|
let footer: string[] = [];
|
|
529
528
|
|
|
530
529
|
const row = (content: string): string => truncateToWidth(content, width, "");
|
|
531
|
-
const guide = (key: string, action: string) => t.fg("
|
|
532
|
-
const guideSep = t.fg("
|
|
530
|
+
const guide = (key: string, action: string) => t.fg("muted", `${key} ${action}`);
|
|
531
|
+
const guideSep = t.fg("muted", " • ");
|
|
533
532
|
|
|
534
533
|
// Tab bar — rendered as the framed top edge (frameLines `top`).
|
|
535
534
|
let top: string | undefined;
|
|
@@ -540,14 +539,14 @@ export class AskQuestionnaire extends Container {
|
|
|
540
539
|
const tag = `${i + 1}.${this.params.questions[i]?.header}`;
|
|
541
540
|
tabParts.push(active ? t.fg("accent", t.bold(tag)) : t.fg("dim", tag));
|
|
542
541
|
}
|
|
543
|
-
top = truncateToWidth(tabParts.join(t.fg("
|
|
542
|
+
top = truncateToWidth(tabParts.join(t.fg("muted", " ")), width, "");
|
|
544
543
|
}
|
|
545
544
|
|
|
546
545
|
// Header chip
|
|
547
546
|
const chip = t.fg("accent", t.bold(this.currentQ.header));
|
|
548
547
|
const prog =
|
|
549
548
|
this.params.questions.length > 1
|
|
550
|
-
?
|
|
549
|
+
? t.fg("muted", ` ${this.currentIndex + 1}/${this.params.questions.length}`)
|
|
551
550
|
: "";
|
|
552
551
|
header.push(row(`${chip}${prog}`));
|
|
553
552
|
|
|
@@ -583,7 +582,7 @@ export class AskQuestionnaire extends Container {
|
|
|
583
582
|
if (!isMulti) {
|
|
584
583
|
const searchVal = this.searchQuery
|
|
585
584
|
? t.fg("text", this.searchQuery)
|
|
586
|
-
: t.fg("
|
|
585
|
+
: t.fg("muted", "type to filter");
|
|
587
586
|
header.push(row(`${t.fg("accent", "Filter:")} ${searchVal}`));
|
|
588
587
|
}
|
|
589
588
|
|
|
@@ -593,7 +592,7 @@ export class AskQuestionnaire extends Container {
|
|
|
593
592
|
const maxOptLines = Math.max(optionLines.length, previewLines.length);
|
|
594
593
|
|
|
595
594
|
if (useSplit) {
|
|
596
|
-
const sep = t.fg("
|
|
595
|
+
const sep = t.fg("muted", SEPARATOR);
|
|
597
596
|
for (let i = 0; i < maxOptLines; i++) {
|
|
598
597
|
const left = truncateToWidth(optionLines[i] ?? "", leftWidth, "", true);
|
|
599
598
|
const right = truncateToWidth(previewLines[i] ?? "", previewWidth, "");
|
|
@@ -643,7 +642,7 @@ export class AskQuestionnaire extends Container {
|
|
|
643
642
|
color: (s) => t.fg("accent", s),
|
|
644
643
|
bg: (s) => t.bg("customMessageBg", s),
|
|
645
644
|
overflowLine: ({ page, totalPages }) =>
|
|
646
|
-
t.fg("
|
|
645
|
+
t.fg("muted", `PgUp/PgDn inspect • ${page}/${totalPages}`),
|
|
647
646
|
});
|
|
648
647
|
this.pager.sync(result);
|
|
649
648
|
return result.lines;
|