@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.
- package/CHANGELOG.md +67 -85
- package/README.md +22 -15
- package/docs/architecture.md +6 -1
- package/docs/configuration.md +4 -3
- package/docs/contract.md +34 -14
- package/docs/remote-events.md +14 -14
- package/package.json +20 -77
- package/skills/ask-user/SKILL.md +2 -0
- package/src/answer-commands.ts +37 -10
- package/src/answer-extraction.ts +155 -115
- package/src/ask-payload-store.ts +23 -1
- package/src/ask-tool-helpers.ts +12 -11
- package/src/ask-tool.ts +2 -0
- package/src/constants/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/pending-ask.ts +128 -0
- package/src/remote-ask.ts +11 -6
- package/src/result-format.ts +13 -3
- package/src/result.ts +1 -1
- package/src/resume-pending-ask.ts +95 -0
- package/src/rpc/controller.ts +2 -1
- package/src/schema.ts +59 -24
- package/src/state/create.ts +1 -1
- package/src/state/normalize.ts +50 -31
- package/src/state/result.ts +3 -0
- package/src/types.ts +1 -0
- package/src/ui/render-helpers.ts +9 -2
- package/src/ui/render-question.ts +39 -7
- package/src/ui/view-models/question.ts +2 -0
package/docs/remote-events.md
CHANGED
|
@@ -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
|
-
- `@
|
|
12
|
-
- `@
|
|
11
|
+
- `@geoqiao/pi-ask:started`
|
|
12
|
+
- `@geoqiao/pi-ask:completed`
|
|
13
13
|
|
|
14
14
|
Remote submit:
|
|
15
15
|
|
|
16
|
-
- `@
|
|
17
|
-
- `@
|
|
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("@
|
|
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("@
|
|
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("@
|
|
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("@
|
|
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("@
|
|
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("@
|
|
156
|
+
pi.events.on("@geoqiao/pi-ask:started", (event: any) => {
|
|
157
157
|
const q = event.questions[0];
|
|
158
158
|
setTimeout(() => {
|
|
159
|
-
pi.events.emit("@
|
|
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.
|
|
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-
|
|
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-
|
|
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
|
-
"@
|
|
96
|
-
"@
|
|
97
|
-
"@earendil-works/pi-
|
|
98
|
-
"
|
|
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
|
-
"
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
}
|
package/skills/ask-user/SKILL.md
CHANGED
|
@@ -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
|
|
package/src/answer-commands.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 {
|
|
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
|
+
}
|