@fyeeme/pi-ask-user-lite 2.0.1 → 2.0.2
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/CHANGELOG.md +6 -0
- package/index.ts +80 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.0.2] - 2026-09-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `ask_user` no longer crashes in RPC mode: `ctx.ui.custom()` is TUI-only (it resolves to `undefined` under RPC), so RPC hosts now degrade to native `select`/`input` dialogs — multi-select via a "Done selecting" pick loop, "Other" via `input`, and timeout auto-selecting the recommended options.
|
|
10
|
+
|
|
5
11
|
## [2.0.1] - 2026-08-27
|
|
6
12
|
|
|
7
13
|
First release under the `@fyeeme/pi-ask-user-lite` name; the entries below describe the 2.0.0 feature set published under the old `@fyeeme/pi-ask-user` name (code is identical apart from the rename).
|
package/index.ts
CHANGED
|
@@ -658,6 +658,80 @@ async function runAskDialog(
|
|
|
658
658
|
return { kind: action.kind, timedOut: action.timedOut === true, state };
|
|
659
659
|
}
|
|
660
660
|
|
|
661
|
+
// ---------------------------------------------------------------------------
|
|
662
|
+
// RPC fallback: native select/input dialogs (no ctx.ui.custom — RPC hosts
|
|
663
|
+
// resolve custom() to undefined; Mode Behavior doc requires a ctx.mode ===
|
|
664
|
+
// "tui" guard around custom()). Mirrors omp's legacy per-question selector:
|
|
665
|
+
// single-select per pick, a "Done selecting" terminal option for multi
|
|
666
|
+
// questions, free-text Other via input, and timeout auto-selection of the
|
|
667
|
+
// recommended options for every unanswered question.
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
|
|
670
|
+
type RpcAskUi = Pick<ExtensionUIContext, "select" | "input">;
|
|
671
|
+
|
|
672
|
+
const RPC_DONE_LABEL = "Done selecting";
|
|
673
|
+
/** Window after the deadline within which an `undefined` pick is attributed
|
|
674
|
+
* to the UI-enforced timeout rather than a user Escape (omp heuristic). */
|
|
675
|
+
const RPC_TIMEOUT_TOLERANCE_MS = 500;
|
|
676
|
+
|
|
677
|
+
async function runAskDialogRpc(
|
|
678
|
+
ui: RpcAskUi,
|
|
679
|
+
questions: AskQuestion[],
|
|
680
|
+
options_: { deadline?: number; signal?: AbortSignal },
|
|
681
|
+
): Promise<DialogOutcome> {
|
|
682
|
+
const answers: Array<{ selectedOptions: string[]; customInput?: string } | undefined> = questions.map(() => undefined);
|
|
683
|
+
const notes: Array<string | undefined> = questions.map(() => undefined);
|
|
684
|
+
const state: DialogState = { index: 0, cursors: [], checked: [], answers, notes };
|
|
685
|
+
const remainingMs = () => (options_.deadline === undefined ? undefined : Math.max(0, options_.deadline - Date.now()));
|
|
686
|
+
const timedOutAt = () => options_.deadline !== undefined && Date.now() >= options_.deadline - RPC_TIMEOUT_TOLERANCE_MS;
|
|
687
|
+
const dialogOpts = () => ({ timeout: remainingMs(), signal: options_.signal });
|
|
688
|
+
|
|
689
|
+
for (const [index, q] of questions.entries()) {
|
|
690
|
+
state.index = index;
|
|
691
|
+
const labels = addRecommendedSuffix(q.options, q.multi ? undefined : q.recommended);
|
|
692
|
+
const tail = q.multi ? [RPC_DONE_LABEL, OTHER_OPTION, CHAT_OPTION] : [OTHER_OPTION, CHAT_OPTION];
|
|
693
|
+
const selected = new Set<string>();
|
|
694
|
+
|
|
695
|
+
for (;;) {
|
|
696
|
+
const choice = await ui.select(q.question, [...labels, ...tail], dialogOpts());
|
|
697
|
+
if (choice === undefined) {
|
|
698
|
+
if (timedOutAt()) {
|
|
699
|
+
return { kind: "submit", timedOut: true, state };
|
|
700
|
+
}
|
|
701
|
+
return { kind: "cancel", timedOut: false, state };
|
|
702
|
+
}
|
|
703
|
+
if (choice === CHAT_OPTION) {
|
|
704
|
+
return { kind: "chat", timedOut: false, state };
|
|
705
|
+
}
|
|
706
|
+
if (choice === OTHER_OPTION) {
|
|
707
|
+
const custom = await ui.input(`Your answer for "${q.header ?? q.question}":`, undefined, dialogOpts());
|
|
708
|
+
if (custom === undefined) {
|
|
709
|
+
if (timedOutAt()) return { kind: "submit", timedOut: true, state };
|
|
710
|
+
return { kind: "cancel", timedOut: false, state };
|
|
711
|
+
}
|
|
712
|
+
if (custom.length > 0) {
|
|
713
|
+
answers[index] = { selectedOptions: [], customInput: custom }; // omp: re-selecting replaces
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
continue; // empty submit declines the custom input — back to the rows
|
|
717
|
+
}
|
|
718
|
+
if (q.multi) {
|
|
719
|
+
if (choice === RPC_DONE_LABEL) {
|
|
720
|
+
answers[index] = { selectedOptions: [...selected] };
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
const label = stripRecommendedSuffix(choice);
|
|
724
|
+
if (selected.has(label)) selected.delete(label);
|
|
725
|
+
else selected.add(label);
|
|
726
|
+
continue; // keep picking until Done
|
|
727
|
+
}
|
|
728
|
+
answers[index] = { selectedOptions: [stripRecommendedSuffix(choice)] };
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return { kind: "submit", timedOut: false, state };
|
|
733
|
+
}
|
|
734
|
+
|
|
661
735
|
function buildResults(questions: AskQuestion[], state: DialogState, timedOut: boolean): QuestionResult[] {
|
|
662
736
|
return questions.map((q, i) => {
|
|
663
737
|
const answer = state.answers[i];
|
|
@@ -725,7 +799,12 @@ const askUserTool: ToolDefinition<typeof AskParamsSchema, AskUserDetails> = {
|
|
|
725
799
|
const deadline =
|
|
726
800
|
typeof params.timeoutSeconds === "number" && params.timeoutSeconds > 0 ? Date.now() + params.timeoutSeconds * 1000 : undefined;
|
|
727
801
|
|
|
728
|
-
|
|
802
|
+
// ctx.ui.custom() is TUI-only (Mode Behavior): RPC hosts resolve it to
|
|
803
|
+
// undefined, so degrade to native select/input dialogs there.
|
|
804
|
+
const outcome =
|
|
805
|
+
ctx.mode === "tui"
|
|
806
|
+
? await runAskDialog(ctx.ui, questions, { deadline, signal })
|
|
807
|
+
: await runAskDialogRpc(ctx.ui, questions, { deadline, signal });
|
|
729
808
|
|
|
730
809
|
if (outcome.kind === "chat") {
|
|
731
810
|
const questionTexts = questions.map((q) => q.question).join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fyeeme/pi-ask-user-lite",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Structured ask_user tool for pi — lets the LLM surface clarifying questions with options, multi-select, recommended defaults, and free-form Other input.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|