@fyeeme/pi-ask-user-lite 2.0.1 → 2.0.3
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 +13 -0
- package/index.ts +91 -4
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.0.3] - 2026-10-01
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- `ask_user` now registers with pi 0.99 `exposure: "model-only"`: declared to the model, unreachable from codemode scripts and nested tool execution (ask-the-user is a model-level decision). No `outputSchema` is declared — model-only tools have no programmatic consumer of structured results. Model-facing behavior is unchanged.
|
|
10
|
+
- Peer dependency floor raised to `@earendil-works/pi-coding-agent >= 0.99.0`; dev toolchain pinned to 0.99.2.
|
|
11
|
+
|
|
12
|
+
## [2.0.2] - 2026-09-09
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `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.
|
|
17
|
+
|
|
5
18
|
## [2.0.1] - 2026-08-27
|
|
6
19
|
|
|
7
20
|
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
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `/tree` inspection.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { ExtensionAPI, ExtensionCommandContext, ExtensionUIContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionToolContext, ExtensionUIContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import { Editor, type EditorTheme, Key, matchesKey, Text, type TUI, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
21
21
|
import { Type } from "typebox";
|
|
@@ -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];
|
|
@@ -687,7 +761,12 @@ const askUserTool: ToolDefinition<typeof AskParamsSchema, AskUserDetails> = {
|
|
|
687
761
|
"Ask the user one or more clarifying questions with selectable options. " +
|
|
688
762
|
"Use when choices have materially different tradeoffs the user must decide. " +
|
|
689
763
|
"The user can always provide free-form input via an automatic 'Other' option.",
|
|
690
|
-
|
|
764
|
+
// pi 0.99: model-only — declared to the model, unreachable from codemode
|
|
765
|
+
// scripts / nested tool execution (ask-the-user is a model-level decision;
|
|
766
|
+
// spec: ask-user-exposure). No outputSchema: model-only tools have no
|
|
767
|
+
// programmatic consumer of structuredContent.
|
|
768
|
+
exposure: "model-only",
|
|
769
|
+
promptSnippet: "Ask the user structured questions with options during execution",
|
|
691
770
|
promptGuidelines: [
|
|
692
771
|
"Use ask_user only after exhausting repo conventions, configs, and docs; reserve it for decisions whose options have materially different tradeoffs.",
|
|
693
772
|
],
|
|
@@ -725,7 +804,12 @@ const askUserTool: ToolDefinition<typeof AskParamsSchema, AskUserDetails> = {
|
|
|
725
804
|
const deadline =
|
|
726
805
|
typeof params.timeoutSeconds === "number" && params.timeoutSeconds > 0 ? Date.now() + params.timeoutSeconds * 1000 : undefined;
|
|
727
806
|
|
|
728
|
-
|
|
807
|
+
// ctx.ui.custom() is TUI-only (Mode Behavior): RPC hosts resolve it to
|
|
808
|
+
// undefined, so degrade to native select/input dialogs there.
|
|
809
|
+
const outcome =
|
|
810
|
+
ctx.mode === "tui"
|
|
811
|
+
? await runAskDialog(ctx.ui, questions, { deadline, signal })
|
|
812
|
+
: await runAskDialogRpc(ctx.ui, questions, { deadline, signal });
|
|
729
813
|
|
|
730
814
|
if (outcome.kind === "chat") {
|
|
731
815
|
const questionTexts = questions.map((q) => q.question).join("\n");
|
|
@@ -889,7 +973,10 @@ async function runAskDemo(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
889
973
|
const run = async (label: string, params: AskParams): Promise<string> => {
|
|
890
974
|
ctx.ui.notify(`ask-demo ${label} — follow the dialog instructions`, "info");
|
|
891
975
|
try {
|
|
892
|
-
|
|
976
|
+
// pi 0.99 split tool and command contexts into sibling branches; the demo
|
|
977
|
+
// only exercises ExtensionContext members (ui/abort), so the cast is safe.
|
|
978
|
+
const toolCtx = ctx as unknown as ExtensionToolContext;
|
|
979
|
+
const result = await askUserTool.execute(`demo-${label}`, params, undefined, undefined, toolCtx);
|
|
893
980
|
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
894
981
|
ctx.ui.notify(`ask-demo ${label} → ${text.split("\n").filter(Boolean).join(" | ").slice(0, 200)}`, "info");
|
|
895
982
|
return text;
|
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.3",
|
|
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",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"typecheck": "tsc"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
47
|
-
"@earendil-works/pi-tui": ">=0.
|
|
46
|
+
"@earendil-works/pi-coding-agent": ">=0.99.0",
|
|
47
|
+
"@earendil-works/pi-tui": ">=0.99.0",
|
|
48
48
|
"typebox": ">=1.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
52
|
-
"@earendil-works/pi-tui": "0.
|
|
51
|
+
"@earendil-works/pi-coding-agent": "0.99.2",
|
|
52
|
+
"@earendil-works/pi-tui": "0.99.2",
|
|
53
53
|
"@types/node": "22.19.19",
|
|
54
54
|
"typebox": "1.1.38",
|
|
55
55
|
"typescript": "5.9.3",
|