@xynogen/pix-ask 0.2.23 → 0.2.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-ask",
3
- "version": "0.2.23",
3
+ "version": "0.2.26",
4
4
  "description": "Pi tool — structured questionnaire UI (ask_user)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@xynogen/pix-pretty": "^1.11.2",
38
+ "@xynogen/pix-pretty": "^1.19.0",
39
39
  "@xynogen/pix-runtime": "^0.8.0",
40
40
  "typebox": "^1.1.38"
41
41
  },
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("dim", " "));
41
+ const line = parts.join(t.fg("muted", " "));
42
42
  return [
43
43
  truncateToWidth(
44
44
  t.fg("accent", "╭─") +
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { modalOverlayOptions } from "@xynogen/pix-pretty/modal-frame";
4
4
  import {
5
5
  dotJoin,
6
6
  formatCollapsedToolRow,
7
+ frameToolResult,
7
8
  hideCollapsedToolCall,
8
9
  pluralize,
9
10
  } from "@xynogen/pix-pretty/utils";
@@ -43,8 +44,7 @@ export default function registerAsk(pi: ExtensionAPI): void {
43
44
  description: `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous.`,
44
45
  promptSnippet: `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`,
45
46
  promptGuidelines: [
46
- `Use ask whenever the user's request is underspecified and you cannot proceed without concrete decisions you can ask up to ${MAX_QUESTIONS} questions per invocation.`,
47
- "Do not stack multiple ask calls back-to-back — group all clarifying questions into one invocation.",
47
+ "Do not stack multiple ask_user calls back-to-backgroup all clarifying questions into one invocation.",
48
48
  ],
49
49
  executionMode: "sequential",
50
50
  parameters: ParamsSchema,
@@ -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("muted", firstQ)}`;
117
+ const head = `${theme.fg("toolTitle", theme.bold("ask_user"))} ${theme.fg("dim", firstQ)}`;
117
118
  text.setText(
118
- dotJoin([head, theme.fg("dim", pluralize(count, "question"))], (s) => theme.fg("dim", s)),
119
+ dotJoin([head, theme.fg("muted", pluralize(count, "question"))], (s) =>
120
+ theme.fg("muted", s),
121
+ ),
119
122
  );
120
123
  return text;
121
124
  },
@@ -130,6 +133,14 @@ export default function registerAsk(pi: ExtensionAPI): void {
130
133
  text.setText(theme.fg("muted", "Waiting for user input…"));
131
134
  return text;
132
135
  }
136
+ if (renderCtx.isError) {
137
+ const error = result.content
138
+ .filter((part) => part.type === "text")
139
+ .map((part) => part.text)
140
+ .join("\n");
141
+ text.setText(error || "Error");
142
+ return frameToolResult(text, theme, true);
143
+ }
133
144
  if (!details || details.cancelled || !details.answers?.length) {
134
145
  // Cancellation is a warning outcome, not success — shared row keeps the
135
146
  // tool identity and a text label (never color alone) for accessibility.
@@ -164,7 +175,7 @@ export default function registerAsk(pi: ExtensionAPI): void {
164
175
  );
165
176
  const header = `${theme.fg("success", "✓")} ${theme.fg("toolTitle", theme.bold("ask_user"))}`;
166
177
  text.setText([header, ...lines].join("\n"));
167
- return text;
178
+ return frameToolResult(text, theme, false);
168
179
  },
169
180
  });
170
181
  });
@@ -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("muted", s),
131
- scrollInfo: (s: string) => this.theme.fg("dim", s),
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("muted", w)}`, inner, ""));
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("dim", "✎")} ${label}`, inner, ""));
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("dim", "→")} ${label}`, inner, ""));
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("dim", "No preview")];
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("muted", l), mdWidth, ""));
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("text", key) + t.fg("dim", ` ${action}`);
532
- const guideSep = t.fg("dim", " • ");
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("dim", " ")), width, "");
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
- ? dim(t)(` ${this.currentIndex + 1}/${this.params.questions.length}`)
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("dim", "type to filter");
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("dim", SEPARATOR);
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("dim", `PgUp/PgDn inspect • ${page}/${totalPages}`),
645
+ t.fg("muted", `PgUp/PgDn inspect • ${page}/${totalPages}`),
647
646
  });
648
647
  this.pager.sync(result);
649
648
  return result.lines;