@geoqiao/pi-ask 1.2.3 → 1.3.1

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.
@@ -8,13 +8,13 @@ Use this for local bridges: status cards, desktop helpers, or approval UIs. Do n
8
8
 
9
9
  Lifecycle:
10
10
 
11
- - `@eko24ive/pi-ask:started`
12
- - `@eko24ive/pi-ask:completed`
11
+ - `@geoqiao/pi-ask:started`
12
+ - `@geoqiao/pi-ask:completed`
13
13
 
14
14
  Remote submit:
15
15
 
16
- - `@eko24ive/pi-ask:submit`
17
- - `@eko24ive/pi-ask:submit-result`
16
+ - `@geoqiao/pi-ask:submit`
17
+ - `@geoqiao/pi-ask:submit-result`
18
18
 
19
19
  ## Started
20
20
 
@@ -25,19 +25,19 @@ type PiAskStartedEvent = {
25
25
  version: 1;
26
26
  flowId: string;
27
27
  toolCallId?: string;
28
- source: "tool" | "answer" | "answer:again" | "ask:replay";
28
+ source: "tool" | "answer" | "answer:again" | "ask:replay" | "ask:resume";
29
29
  title?: string;
30
30
  questions: AskQuestion[];
31
31
  createdAt: number;
32
32
  };
33
33
  ```
34
34
 
35
- Use `flowId` for submit/correlation. Use `questions[].id` and `questions[].options[].value` for answers.
35
+ Use `flowId` for submit/correlation. Use `questions[].id` and `questions[].options[].value` for answers. `ask:resume` identifies a form recovered from an interrupted tool call. Started-event options preserve the optional `recommended` boolean as presentation metadata only.
36
36
 
37
37
  ## Submit an answer
38
38
 
39
39
  ```ts
40
- pi.events.emit("@eko24ive/pi-ask:submit", {
40
+ pi.events.emit("@geoqiao/pi-ask:submit", {
41
41
  version: 1,
42
42
  requestId: `bridge-${Date.now()}`,
43
43
  flowId,
@@ -73,7 +73,7 @@ Rules:
73
73
  ## Cancel
74
74
 
75
75
  ```ts
76
- pi.events.emit("@eko24ive/pi-ask:submit", {
76
+ pi.events.emit("@geoqiao/pi-ask:submit", {
77
77
  version: 1,
78
78
  requestId: `bridge-${Date.now()}`,
79
79
  flowId,
@@ -111,7 +111,7 @@ type PiAskCompletedEvent = {
111
111
  version: 1;
112
112
  flowId: string;
113
113
  toolCallId?: string;
114
- source: "tool" | "answer" | "answer:again" | "ask:replay";
114
+ source: "tool" | "answer" | "answer:again" | "ask:replay" | "ask:resume";
115
115
  result: AskResult;
116
116
  completedAt: number;
117
117
  };
@@ -121,11 +121,11 @@ type PiAskCompletedEvent = {
121
121
 
122
122
  ```ts
123
123
  export default function piAskBridge(pi: any) {
124
- pi.events.on("@eko24ive/pi-ask:started", (event: any) => {
124
+ pi.events.on("@geoqiao/pi-ask:started", (event: any) => {
125
125
  const question = event.questions[0];
126
126
  const option = question.options[0];
127
127
 
128
- pi.events.emit("@eko24ive/pi-ask:submit", {
128
+ pi.events.emit("@geoqiao/pi-ask:submit", {
129
129
  version: 1,
130
130
  requestId: `bridge-${Date.now()}`,
131
131
  flowId: event.flowId,
@@ -138,7 +138,7 @@ export default function piAskBridge(pi: any) {
138
138
  });
139
139
  });
140
140
 
141
- pi.events.on("@eko24ive/pi-ask:submit-result", (event: any) => {
141
+ pi.events.on("@geoqiao/pi-ask:submit-result", (event: any) => {
142
142
  if (!event.ok) console.error(event.error, event.message);
143
143
  });
144
144
  }
@@ -153,10 +153,10 @@ Create a temporary bridge and run pi with only this repo extension plus the brid
153
153
  ```bash
154
154
  cat > /tmp/pi-ask-smoke.ts <<'EOF'
155
155
  export default function smoke(pi: any) {
156
- pi.events.on("@eko24ive/pi-ask:started", (event: any) => {
156
+ pi.events.on("@geoqiao/pi-ask:started", (event: any) => {
157
157
  const q = event.questions[0];
158
158
  setTimeout(() => {
159
- pi.events.emit("@eko24ive/pi-ask:submit", {
159
+ pi.events.emit("@geoqiao/pi-ask:submit", {
160
160
  version: 1,
161
161
  requestId: `smoke-${Date.now()}`,
162
162
  flowId: event.flowId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoqiao/pi-ask",
3
- "version": "1.2.3",
3
+ "version": "1.3.1",
4
4
  "description": "Pi package that adds an interactive ask_user clarification tool.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,17 +32,6 @@
32
32
  "LICENSE",
33
33
  "CHANGELOG.md"
34
34
  ],
35
- "scripts": {
36
- "dev": "sh -c 'ROOT=\"$PWD\"; TARGET=\"${1:-.}\"; if [ \"$TARGET\" = \"--\" ]; then TARGET=\"${2:-.}\"; fi; cd \"$TARGET\" && pi --no-extensions --no-skills --no-prompt-templates --no-themes --no-context-files -e \"$ROOT/src/index.ts\" --skill \"$ROOT/skills/ask-user\"' --",
37
- "test": "node --test tests/*.test.ts",
38
- "typecheck": "tsc -p tsconfig.json",
39
- "format": "biome format --write .",
40
- "lint": "biome lint --write .",
41
- "check": "ultracite check",
42
- "check:ci": "biome ci .",
43
- "fix": "ultracite fix",
44
- "pack:check": "pnpm pack --dry-run"
45
- },
46
35
  "pi": {
47
36
  "extensions": [
48
37
  "./src/index.ts"
@@ -63,12 +52,23 @@
63
52
  },
64
53
  "devDependencies": {
65
54
  "@biomejs/biome": "2.4.12",
66
- "@earendil-works/pi-ai": "0.79.9",
67
- "@earendil-works/pi-coding-agent": "0.79.9",
68
- "@earendil-works/pi-tui": "0.79.9",
69
- "typebox": "1.1.38",
55
+ "@earendil-works/pi-ai": "0.84.2",
56
+ "@earendil-works/pi-coding-agent": "0.84.2",
57
+ "@earendil-works/pi-tui": "0.84.2",
58
+ "typebox": "1.3.7",
70
59
  "@types/node": "25.6.0",
71
60
  "typescript": "6.0.2",
72
61
  "ultracite": "7.6.0"
62
+ },
63
+ "scripts": {
64
+ "dev": "sh -c 'ROOT=\"$PWD\"; TARGET=\"${1:-.}\"; if [ \"$TARGET\" = \"--\" ]; then TARGET=\"${2:-.}\"; fi; cd \"$TARGET\" && pi --no-extensions --no-skills --no-prompt-templates --no-themes --no-context-files -e \"$ROOT/src/index.ts\" --skill \"$ROOT/skills/ask-user\"' --",
65
+ "test": "node --test tests/*.test.ts",
66
+ "typecheck": "tsc -p tsconfig.json",
67
+ "format": "biome format --write .",
68
+ "lint": "biome lint --write .",
69
+ "check": "ultracite check",
70
+ "check:ci": "biome ci .",
71
+ "fix": "ultracite fix",
72
+ "pack:check": "pnpm pack --dry-run"
73
73
  }
74
- }
74
+ }
@@ -1,110 +1,70 @@
1
1
  ---
2
2
  name: ask-user
3
- description: "Use ask_user as a decision, research, and requirements gate before ambiguous or high-stakes choices."
3
+ description: "Use ask_user when context review leaves a critical requirement, outcome-changing preference, or high-impact authorization gap unresolved, or the user explicitly requests an interview, requirements gathering, or interactive questions. Not for multiple options or comparison/research requests alone."
4
4
  metadata:
5
- short-description: Decision, research, and requirements gate
5
+ short-description: Clarify material gaps after reading context
6
6
  ---
7
7
 
8
- # Ask User decision/research gate
8
+ # Ask User: clarify material gaps
9
9
 
10
- Use this skill to force explicit user alignment before consequential decisions, preference-sensitive planning, research scoping, or requirements gathering.
10
+ Read available context before asking. Use `ask_user` to resolve a gap that materially changes the result, not to transfer routine decisions back to the user.
11
11
 
12
- This skill is for decision control, research scoping, requirements gathering, and preference-sensitive planning, not general chat.
12
+ ## When to ask
13
13
 
14
- ## Trigger
14
+ Read relevant code, docs, conversation, prior answers, and the user's existing requirements and authorization. Resolve factual uncertainty through available evidence rather than asking the user to repeat it. Do not invent preferences or permission from a code convention.
15
15
 
16
- Classify the next step as one of:
16
+ Use `ask_user` when that review still leaves:
17
17
 
18
- - `high_stakes`
19
- - `ambiguous`
20
- - `both`
21
- - `clear`
18
+ - a critical requirement missing or conflicting, so proceeding would produce materially different results;
19
+ - an important preference unresolved that materially changes the outcome and cannot be inferred from the user's requirements;
20
+ - missing authorization for a consequential or hard-to-reverse next action beyond the approved scope.
22
21
 
23
- Use `ask_user` when the next step is ambiguous, preference-sensitive, or high-stakes and the user has not already made the decision explicitly.
22
+ Also use `ask_user` when the user explicitly requests an interview, requirements gathering, or interactive questions. Ask about the current topic and use existing answers rather than restarting discovery. If the user requests a written questionnaire or checklist instead, provide that artifact in prose.
24
23
 
25
- Use `ask_user` for any domain where user input changes the plan, recommendation, research direction, output format, criteria, constraints, or next action.
24
+ ## When to proceed
26
25
 
27
- Also use `ask_user` when the user asks to gather requirements, interview them, ask questions, scope research, plan work, compare options, or answer a set of open product/design/architecture/research questions. Do not respond with a plain-text questionnaire unless the user explicitly asks for a checklist or written questionnaire.
26
+ - Do not ask about facts or requirements already established in code, docs, or the conversation.
27
+ - Do not reconfirm settled choices or existing authorization. Carry out reversible steps and routine implementation details within the authorized scope; state useful assumptions without turning them into approval requests.
28
+ - Multiple viable options do not by themselves justify a question. Labels such as architecture, schema, naming, UX, planning, or research do not establish a material gap or a need for new permission.
29
+ - Complete clear comparison/research requests first. Analyze evidence and trade-offs, give conditional conclusions where appropriate, and do not automatically turn a comparison into an interview. Ask only if a remaining material gap actually blocks the requested analysis or next action.
30
+ - Treat "your call" as delegation within the stated scope, not as missing preference information. Do not let delegated autonomy waive safety boundaries or expand authorization to unrelated high-impact actions.
28
31
 
29
- ### Treat as `high_stakes` when the next step changes:
32
+ ## Keep questions focused
30
33
 
31
- - architecture, schema, API contract, deployment, or security posture
32
- - production-facing behavior in a costly-to-undo way
33
- - large refactors, migrations, or destructive edits
34
- - legal, financial, medical, career, hiring, vendor, purchasing, travel, or other costly-to-reverse decisions
35
- - public-facing claims, sensitive communications, or consequential recommendations
34
+ Explain the blocking gap and its consequence briefly, then ask one concrete decision per question using `ask_user`, not a plain-text multiple-choice detour. Ask only current blockers; bundle related blockers only if they can be answered independently. In a requested interview, keep each batch on the current topic and follow the user's requested pacing.
36
35
 
37
- ### Treat as `ambiguous` when:
36
+ Use the answer explicitly and preserve resolved decisions. After an elaboration or note, answer the clarification first; use a structured follow-up only if a material decision still blocks progress. Respect `continuation.preservedAnswers` and revisit only affected unresolved questions. Reopen a settled decision only when materially new information changes its assumptions, scope, or consequences, and explain what changed.
38
37
 
39
- - requirements, goals, constraints, evaluation criteria, or success criteria are missing/conflicting
40
- - multiple valid options exist and the trade-off is preference-sensitive
41
- - research scope, audience, budget, timeline, risk tolerance, or output format is unclear
42
- - you would otherwise make a material assumption
38
+ Cancellation, missing answers, or ambiguous responses are not approval for high-risk actions. If authorization remains missing, leave that action blocked and explain the boundary; do not automatically repeat the same question or silently choose a risky default. Continue only independent work still covered by existing authorization. `cancelled: false` and `required: true` do not prove an answer or approval exists: required is advisory, and RPC dismissal can skip a question without cancelling the flow.
43
39
 
44
- ## Handshake (required)
40
+ ## Payload and presentation
45
41
 
46
- 1. Gather evidence first from code/docs/tools.
47
- 2. Summarize neutral context (current state, constraints, trade-offs, recommendation).
48
- 3. Ask one focused `ask_user` decision question, or bundle 2-5 closely related questions when the user is explicitly in requirement-gathering/interview mode.
49
- 4. Restate the user decision and proceed explicitly with it.
50
- 5. Re-open only for materially new ambiguity.
42
+ - Include a stable question `id`, non-empty `prompt`, and a non-empty machine-readable `value` and visible `label` for every option. Question ids must be unique within a call; option values must be unique within a question.
43
+ - Keep labels short and options distinct and outcome-oriented. Do not add filler options. Use `description` for meaningful trade-offs.
44
+ - Mark grounded preferences with `recommended: true` and explain the reason in `description`; recommendations are presentation-only and never preselected.
45
+ - Choose `single` for one expected answer, `multi` for multiple possible selections, and `preview` for richer comparison detail. Every declared preview option must include non-empty `preview` text; descriptions alone do not suffice.
46
+ - TUI provides tabbed questions, native single/multi selection, a preview pane, and an internal `Type your own` fallback for every question type, including preview.
47
+ - RPC presents questions sequentially with one real option or `Type something…` per question. Use typed input for multiple choices; previews and descriptions flatten into option text. Do not promise same-screen forms, native checkbox cards, a custom preview pane, notes, or a review tab in RPC.
51
48
 
52
- ## Question spew prevention
49
+ ## Examples and behavioral evaluation cases
53
50
 
54
- Before sending any assistant response that contains 2+ substantive questions for the user, stop and decide whether those questions should be interactive.
51
+ These are expected behaviors for model evaluation, not evidence that a model follows the policy. Static prompt tests check registered wording and tool constraints only.
55
52
 
56
- Use `ask_user` instead of prose when:
53
+ | Context / request | Expected behavior |
54
+ | --- | --- |
55
+ | "Fix this typo and update its test" with the target and expected text supplied | Read the relevant files and make the small change; no interview about naming, style, or alternatives. |
56
+ | "Use the existing helper; implement the approved plan" | Follow the helper and plan; do not re-ask which approach to use. |
57
+ | "Add data expiry" with no retention period in code, docs, or prior requirements | Ask for the retention requirement before implementing deletion semantics. |
58
+ | "Draft the customer announcement" but the audience and disclosure scope remain unresolved and would materially change the message | Ask only for the missing consequential content preferences, not every possible tone choice. |
59
+ | "Prepare a migration plan" followed by a proposed production migration or irreversible deletion | Ask for explicit authorization before execution; permission to plan is not permission to execute. |
60
+ | "Interview me to gather requirements" | Use `ask_user` for the current interview topic, respecting the requested pacing and prior answers. |
61
+ | "Compare SQLite and PostgreSQL for a small local app" | Analyze the comparison first, using evidence and conditional trade-offs; do not automatically ask the user to choose a database or define a full product brief. |
62
+ | "Choose the local implementation details yourself" with several reversible approaches | Use established patterns within scope and state useful assumptions; do not ask merely because alternatives exist. |
63
+ | A high-risk authorization question is cancelled, skipped, or answered vaguely | Do not execute the action or infer approval from completion metadata; explain the unresolved boundary. |
64
+ | A chosen migration was approved for staging, but new evidence shows the target is production | Reopen only the changed target/authorization decision; do not restart the whole interview. |
57
65
 
58
- - the questions are meant to collect requirements, goals, constraints, preferences, scope, priorities, criteria, or missing context
59
- - answers will materially change the next artifact, recommendation, research direction, plan, implementation, architecture, schema, UX, stack choice, or decision criteria
60
- - the user previously corrected you with phrases like "ask those questions", "ask interactively", or "use ask_user"
61
-
62
- Plain-text questions are acceptable only when:
63
-
64
- - the user asked for a written checklist/list of open questions
65
- - the questions are rhetorical or purely explanatory
66
- - there is exactly one small factual clarification and an interactive flow would be heavier than needed
67
-
68
- If there are too many questions, group them into the smallest coherent `ask_user` batches and ask the highest-impact batch first.
69
-
70
- ## Question budget and escalation
71
-
72
- - Max 1 `ask_user` call per decision boundary in normal cases.
73
- - Max 2 calls for the same boundary if first answer is unclear/cancelled.
74
- - Never re-ask the same trade-off without new evidence.
75
-
76
- Attempt 2 (only if needed) must be narrower and include:
77
-
78
- - `Proceed with recommended option`
79
- - `Choose another option`
80
- - `Stop for now`
81
-
82
- After attempt 2:
83
-
84
- - for `high_stakes` or `both`: stop as blocked until explicit decision
85
- - for `ambiguous` only: if user delegates ("your call"), proceed with the most reversible default and state assumptions
86
-
87
- ## ask_user payload quality
88
-
89
- - Ask one concrete decision at a time.
90
- - Provide clear, distinct options. Do not add filler options.
91
- - Choose question type from semantics: `single` means one answer is expected, `multi` means multiple answers could reasonably be selected, and `preview` means options need preview-pane detail with non-empty preview text.
92
- - Avoid defaulting mechanically; infer from whether options are mutually exclusive, can coexist, or need preview-pane detail.
93
- - Keep option labels short and outcome-oriented.
94
- - Include trade-off descriptions when non-obvious.
95
- - For research/planning, ask about goals, constraints, evaluation criteria, audience, budget, timeline, risk tolerance, and desired output only when they materially affect the result.
96
- - Prefer non-`preview` questions when a free-form answer may be useful, since those include an internal `Type your own` option.
97
-
98
- ## Guardrails
99
-
100
- - Do not ask before reading available context.
101
- - Do not use for trivial formatting/style micro-decisions.
102
- - Do not continue implementation after unclear high-stakes answers.
66
+ For actual model evaluation, run these cases in fresh sessions with the local extension alone and with the bundled skill loaded. Inspect context reads, whether and what the model asks, and its next action; include prior-answer and cancellation turns. Record model/version, loaded instructions, observed behavior, and false positives/negatives. Never execute real high-risk actions for these checks.
103
67
 
104
68
  ## Conflict rule
105
69
 
106
- If this skill conflicts with implementation behavior or tests, the project contract wins:
107
-
108
- 1. `docs/contract.md`
109
- 2. `tests/*.test.ts`
110
- 3. this skill
70
+ This guidance is advisory, not a runtime authorization mechanism. For tool behavior, [`docs/contract.md`](../../docs/contract.md) and the package tests take precedence over this skill.
@@ -25,6 +25,7 @@ import { runAskFlow } from "./ui/controller.ts";
25
25
 
26
26
  interface AssistantTextSource {
27
27
  entryId: string;
28
+ previousUserText?: string;
28
29
  text: string;
29
30
  }
30
31
 
@@ -192,11 +193,13 @@ function runExtractionUi(
192
193
  loader.onAbort = () => doneOnce({ cancelled: true });
193
194
  extractAskParams({
194
195
  assistantText: assistant.text,
195
- auth: selected.auth,
196
+ previousUserText: assistant.previousUserText,
197
+ complete: (model, context, options) =>
198
+ ctx.modelRegistry.complete(model, context, options),
196
199
  model: selected.model,
197
200
  onRetry: (attempt, maxRetries) => {
198
201
  ctx.ui.notify(
199
- `Retrying extraction JSON repair (${attempt}/${maxRetries})...`,
202
+ `Retrying question extraction (${attempt}/${maxRetries})...`,
200
203
  "info"
201
204
  );
202
205
  },
@@ -346,16 +349,40 @@ function findLatestAssistantText(
346
349
  error: `Latest assistant message is incomplete (${message.stopReason}); wait for it to finish, then run /answer again.`,
347
350
  };
348
351
  }
349
- const text = message.content
350
- .filter(
351
- (part): part is { text: string; type: "text" } => part.type === "text"
352
- )
353
- .map((part) => part.text)
354
- .join("\n")
355
- .trim();
352
+ const text = extractTextContent(message.content);
356
353
  if (text) {
357
- return { entryId: entry.id, text };
354
+ return {
355
+ entryId: entry.id,
356
+ previousUserText: findPreviousUserText(branch, index),
357
+ text,
358
+ };
358
359
  }
359
360
  }
360
361
  return { error: "No assistant message found to extract questions from." };
361
362
  }
363
+
364
+ function findPreviousUserText(
365
+ branch: ReturnType<ExtensionContext["sessionManager"]["getBranch"]>,
366
+ beforeIndex: number
367
+ ): string | undefined {
368
+ for (let index = beforeIndex - 1; index >= 0; index--) {
369
+ const entry = branch[index];
370
+ if (entry.type !== "message" || entry.message.role !== "user") {
371
+ continue;
372
+ }
373
+ return extractTextContent(entry.message.content) || undefined;
374
+ }
375
+ return;
376
+ }
377
+
378
+ function extractTextContent(
379
+ content: string | Array<{ text?: string; type: string }>
380
+ ): string {
381
+ if (typeof content === "string") {
382
+ return content.trim();
383
+ }
384
+ return content
385
+ .flatMap((part) => (part.type === "text" && part.text ? [part.text] : []))
386
+ .join("\n")
387
+ .trim();
388
+ }