@geoqiao/pi-ask 1.2.2 → 1.3.0

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,16 +1,17 @@
1
1
  {
2
2
  "name": "@geoqiao/pi-ask",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Pi package that adds an interactive ask_user clarification tool.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/geoqiao/pi-ask.git"
9
+ "url": "git+https://github.com/geoqiao/pi-tools.git",
10
+ "directory": "packages/pi-ask"
10
11
  },
11
- "homepage": "https://github.com/geoqiao/pi-ask#readme",
12
+ "homepage": "https://github.com/geoqiao/pi-tools/tree/main/packages/pi-ask#readme",
12
13
  "bugs": {
13
- "url": "https://github.com/geoqiao/pi-ask/issues"
14
+ "url": "https://github.com/geoqiao/pi-tools/issues"
14
15
  },
15
16
  "keywords": [
16
17
  "pi-package",
@@ -31,19 +32,6 @@
31
32
  "LICENSE",
32
33
  "CHANGELOG.md"
33
34
  ],
34
- "scripts": {
35
- "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\"' --",
36
- "test": "node --test tests/*.test.ts",
37
- "typecheck": "tsc -p tsconfig.json",
38
- "format": "biome format --write .",
39
- "lint": "biome lint --write .",
40
- "check": "ultracite check",
41
- "check:ci": "biome ci .",
42
- "fix": "ultracite fix",
43
- "commit": "cz",
44
- "commitlint": "commitlint",
45
- "release": "semantic-release"
46
- },
47
35
  "pi": {
48
36
  "extensions": [
49
37
  "./src/index.ts"
@@ -56,34 +44,6 @@
56
44
  "access": "public",
57
45
  "provenance": true
58
46
  },
59
- "release": {
60
- "repositoryUrl": "https://github.com/geoqiao/pi-ask.git",
61
- "branches": [
62
- "main"
63
- ],
64
- "plugins": [
65
- "@semantic-release/commit-analyzer",
66
- "@semantic-release/release-notes-generator",
67
- [
68
- "@semantic-release/changelog",
69
- {
70
- "changelogFile": "CHANGELOG.md"
71
- }
72
- ],
73
- "@semantic-release/npm",
74
- "@semantic-release/github",
75
- [
76
- "@semantic-release/git",
77
- {
78
- "assets": [
79
- "package.json",
80
- "CHANGELOG.md"
81
- ],
82
- "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
83
- }
84
- ]
85
- ]
86
- },
87
47
  "peerDependencies": {
88
48
  "@earendil-works/pi-coding-agent": "*",
89
49
  "@earendil-works/pi-tui": "*",
@@ -92,40 +52,23 @@
92
52
  },
93
53
  "devDependencies": {
94
54
  "@biomejs/biome": "2.4.12",
95
- "@commitlint/cli": "^19.8.1",
96
- "@commitlint/config-conventional": "^19.8.1",
97
- "@earendil-works/pi-ai": "0.79.9",
98
- "@earendil-works/pi-coding-agent": "0.79.9",
99
- "@earendil-works/pi-tui": "0.79.9",
100
- "@semantic-release/changelog": "^6.0.3",
101
- "@semantic-release/commit-analyzer": "^13.0.1",
102
- "@semantic-release/git": "^10.0.1",
103
- "@semantic-release/github": "^12.0.6",
104
- "@semantic-release/npm": "^13.1.5",
105
- "@semantic-release/release-notes-generator": "^14.1.0",
106
- "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",
107
59
  "@types/node": "25.6.0",
108
- "commitizen": "^4.3.1",
109
- "cz-conventional-changelog": "^3.3.0",
110
- "lefthook": "^2.1.6",
111
- "semantic-release": "^25.0.3",
112
60
  "typescript": "6.0.2",
113
61
  "ultracite": "7.6.0"
114
62
  },
115
- "config": {
116
- "commitizen": {
117
- "path": "./node_modules/cz-conventional-changelog"
118
- }
119
- },
120
- "pnpm": {
121
- "overrides": {
122
- "@earendil-works/pi-agent-core": "0.79.9",
123
- "@earendil-works/pi-ai": "0.79.9",
124
- "@earendil-works/pi-tui": "0.79.9",
125
- "@earendil-works/pi-coding-agent": "0.79.9"
126
- },
127
- "ignoredBuiltDependencies": [
128
- "lefthook"
129
- ]
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"
130
73
  }
131
- }
74
+ }
@@ -92,6 +92,8 @@ After attempt 2:
92
92
  - Avoid defaulting mechanically; infer from whether options are mutually exclusive, can coexist, or need preview-pane detail.
93
93
  - Keep option labels short and outcome-oriented.
94
94
  - Include trade-off descriptions when non-obvious.
95
+ - Include a stable question `id`, non-empty `prompt`, and a non-empty `value` and visible `label` for every option.
96
+ - Mark grounded preferences with `recommended: true` and explain the reason in `description`; recommendations are presentation-only and never preselected.
95
97
  - 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
98
  - Prefer non-`preview` questions when a free-form answer may be useful, since those include an internal `Type your own` option.
97
99
 
@@ -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
+ }