@alexleekt/pi-ask-user-glimpse 0.3.2 → 0.5.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 +85 -0
- package/CONTRIBUTING.md +15 -6
- package/README.md +75 -46
- package/constants/abbreviations.ts +6 -0
- package/constants/stopwords.ts +42 -0
- package/dist/index.html +950 -391
- package/index.ts +374 -211
- package/package.json +60 -9
- package/shared/ask-user.ts +11 -0
- package/tool/ask-user.ts +76 -306
- package/tool/response-formatter.ts +3 -2
- package/fallback/terminal-prompt.ts +0 -191
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexleekt/pi-ask-user-glimpse",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Ask better questions. Get better answers. Rich native WebView dialogs for the Pi agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi-extension",
|
|
9
|
+
"pi",
|
|
9
10
|
"glimpseui",
|
|
10
11
|
"ask_user",
|
|
11
12
|
"interactive"
|
|
@@ -30,8 +31,8 @@
|
|
|
30
31
|
},
|
|
31
32
|
"files": [
|
|
32
33
|
"index.ts",
|
|
34
|
+
"constants",
|
|
33
35
|
"tool",
|
|
34
|
-
"fallback",
|
|
35
36
|
"shared",
|
|
36
37
|
"types",
|
|
37
38
|
"dist",
|
|
@@ -41,25 +42,73 @@
|
|
|
41
42
|
"LICENSE"
|
|
42
43
|
],
|
|
43
44
|
"scripts": {
|
|
44
|
-
"build": "
|
|
45
|
-
"build:css": "
|
|
46
|
-
"build:webview": "
|
|
45
|
+
"build": "wireit",
|
|
46
|
+
"build:css": "wireit",
|
|
47
|
+
"build:webview": "wireit",
|
|
47
48
|
"typecheck": "tsc --noEmit",
|
|
48
49
|
"check": "npm pack --dry-run",
|
|
49
50
|
"prepack": "npm run build",
|
|
50
51
|
"dev:webview": "npx vite --config ./webview/vite.config.ts",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"test:coverage": "vitest run --coverage",
|
|
55
|
+
"test:e2e": "playwright test",
|
|
56
|
+
"test:e2e:ui": "playwright test --ui",
|
|
57
|
+
"validate": "test -f dist/index.html && grep -q 'ASK_USER_PAYLOAD' dist/index.html && echo \"✓ dist/index.html ready\" || echo \"✗ Run npm run build first\"",
|
|
58
|
+
"validate:gui": "echo \"validate:gui requires manual testing with pi extension load\""
|
|
59
|
+
},
|
|
60
|
+
"wireit": {
|
|
61
|
+
"build": {
|
|
62
|
+
"dependencies": [
|
|
63
|
+
"build:css",
|
|
64
|
+
"build:webview"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"build:css": {
|
|
68
|
+
"command": "npx tailwindcss -c ./webview/tailwind.config.js -i ./webview/src/index.css -o ./webview/src/index.generated.css --content './webview/index.html,./webview/src/**/*.{js,ts,jsx,tsx}'",
|
|
69
|
+
"files": [
|
|
70
|
+
"./webview/src/**/*.css",
|
|
71
|
+
"./webview/src/**/*.{ts,tsx,js,jsx}",
|
|
72
|
+
"./webview/tailwind.config.js",
|
|
73
|
+
"./webview/postcss.config.js",
|
|
74
|
+
"./webview/index.html"
|
|
75
|
+
],
|
|
76
|
+
"output": [
|
|
77
|
+
"./webview/src/index.generated.css"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"build:webview": {
|
|
81
|
+
"command": "npx vite build --config ./webview/vite.config.ts",
|
|
82
|
+
"files": [
|
|
83
|
+
"./webview/src/**/*.{ts,tsx}",
|
|
84
|
+
"./webview/index.html",
|
|
85
|
+
"./webview/vite.config.ts",
|
|
86
|
+
"./webview/tsconfig.json",
|
|
87
|
+
"./webview/postcss.config.js",
|
|
88
|
+
"./webview/src/index.generated.css"
|
|
89
|
+
],
|
|
90
|
+
"output": [
|
|
91
|
+
"./dist/index.html"
|
|
92
|
+
],
|
|
93
|
+
"dependencies": [
|
|
94
|
+
"build:css"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
53
97
|
},
|
|
54
98
|
"dependencies": {
|
|
55
99
|
"@alexleekt/pi-shared": "^0.1.0",
|
|
56
100
|
"glimpseui": "^0.8.1"
|
|
57
101
|
},
|
|
58
102
|
"devDependencies": {
|
|
103
|
+
"@playwright/test": "^1.60.0",
|
|
104
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
105
|
+
"@testing-library/react": "^16.3.2",
|
|
59
106
|
"@types/react": "^18.3.0",
|
|
60
107
|
"@types/react-dom": "^18.3.0",
|
|
61
108
|
"@vitejs/plugin-react": "^4.3.0",
|
|
109
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
62
110
|
"autoprefixer": "^10.4.20",
|
|
111
|
+
"jsdom": "^29.1.1",
|
|
63
112
|
"marked": "^15.0.12",
|
|
64
113
|
"mermaid": "^11.15.0",
|
|
65
114
|
"playwright": "^1.60.0",
|
|
@@ -69,7 +118,9 @@
|
|
|
69
118
|
"tailwindcss": "^3.4.14",
|
|
70
119
|
"typescript": "^5.6.0",
|
|
71
120
|
"vite": "^5.4.0",
|
|
72
|
-
"vite-plugin-singlefile": "^2.0.0"
|
|
121
|
+
"vite-plugin-singlefile": "^2.0.0",
|
|
122
|
+
"vitest": "^4.1.7",
|
|
123
|
+
"wireit": "^0.14.12"
|
|
73
124
|
},
|
|
74
125
|
"peerDependencies": {
|
|
75
126
|
"@earendil-works/pi-ai": "*",
|
package/shared/ask-user.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
export interface QuestionOption {
|
|
10
10
|
title: string;
|
|
11
11
|
description?: string;
|
|
12
|
+
recommended?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface Question {
|
|
@@ -18,16 +19,23 @@ export interface Question {
|
|
|
18
19
|
allowMultiple?: boolean;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export type ThemeMode = "light" | "dark" | "system";
|
|
23
|
+
export type AnimationLevel = "none" | "minimal" | "all";
|
|
24
|
+
|
|
21
25
|
export interface AskUserPayload {
|
|
22
26
|
type: "single-select" | "multi-select" | "questionnaire" | "freeform";
|
|
23
27
|
question: string;
|
|
24
28
|
context?: string;
|
|
29
|
+
contextFormat?: "markdown" | "html";
|
|
25
30
|
options: QuestionOption[];
|
|
26
31
|
questions?: Question[];
|
|
27
32
|
allowMultiple: boolean;
|
|
28
33
|
allowFreeform: boolean;
|
|
29
34
|
allowComment: boolean;
|
|
30
35
|
allowSkip?: boolean;
|
|
36
|
+
sessionName?: string;
|
|
37
|
+
theme?: ThemeMode;
|
|
38
|
+
animationLevel?: AnimationLevel;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
export interface QuestionnaireDetail {
|
|
@@ -36,3 +44,6 @@ export interface QuestionnaireDetail {
|
|
|
36
44
|
kind: "selection" | "freeform";
|
|
37
45
|
comment?: string;
|
|
38
46
|
}
|
|
47
|
+
|
|
48
|
+
/** Sentinel title used for the freeform "not listed" option in select dialogs. */
|
|
49
|
+
export const FREEFORM_OPTION_TITLE = "My answer isn't listed above";
|
package/tool/ask-user.ts
CHANGED
|
@@ -4,300 +4,21 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { prompt } from "glimpseui";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import type {
|
|
8
|
+
AnimationLevel,
|
|
9
|
+
AskUserPayload,
|
|
10
|
+
Question,
|
|
11
|
+
ThemeMode,
|
|
12
|
+
} from "../shared/ask-user.js";
|
|
9
13
|
import { formatResponse } from "./response-formatter.js";
|
|
10
14
|
|
|
11
15
|
const _require = createRequire(import.meta.url);
|
|
12
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"the",
|
|
19
|
-
"is",
|
|
20
|
-
"are",
|
|
21
|
-
"was",
|
|
22
|
-
"were",
|
|
23
|
-
"be",
|
|
24
|
-
"been",
|
|
25
|
-
"being",
|
|
26
|
-
"have",
|
|
27
|
-
"has",
|
|
28
|
-
"had",
|
|
29
|
-
"do",
|
|
30
|
-
"does",
|
|
31
|
-
"did",
|
|
32
|
-
"will",
|
|
33
|
-
"would",
|
|
34
|
-
"could",
|
|
35
|
-
"should",
|
|
36
|
-
"may",
|
|
37
|
-
"might",
|
|
38
|
-
"must",
|
|
39
|
-
"shall",
|
|
40
|
-
"can",
|
|
41
|
-
"need",
|
|
42
|
-
"ought",
|
|
43
|
-
"used",
|
|
44
|
-
"to",
|
|
45
|
-
"of",
|
|
46
|
-
"in",
|
|
47
|
-
"for",
|
|
48
|
-
"on",
|
|
49
|
-
"with",
|
|
50
|
-
"at",
|
|
51
|
-
"by",
|
|
52
|
-
"from",
|
|
53
|
-
"as",
|
|
54
|
-
"into",
|
|
55
|
-
"through",
|
|
56
|
-
"during",
|
|
57
|
-
"before",
|
|
58
|
-
"after",
|
|
59
|
-
"above",
|
|
60
|
-
"below",
|
|
61
|
-
"between",
|
|
62
|
-
"under",
|
|
63
|
-
"again",
|
|
64
|
-
"further",
|
|
65
|
-
"then",
|
|
66
|
-
"once",
|
|
67
|
-
"here",
|
|
68
|
-
"there",
|
|
69
|
-
"when",
|
|
70
|
-
"where",
|
|
71
|
-
"why",
|
|
72
|
-
"how",
|
|
73
|
-
"all",
|
|
74
|
-
"each",
|
|
75
|
-
"few",
|
|
76
|
-
"more",
|
|
77
|
-
"most",
|
|
78
|
-
"other",
|
|
79
|
-
"some",
|
|
80
|
-
"such",
|
|
81
|
-
"no",
|
|
82
|
-
"nor",
|
|
83
|
-
"not",
|
|
84
|
-
"only",
|
|
85
|
-
"own",
|
|
86
|
-
"same",
|
|
87
|
-
"so",
|
|
88
|
-
"than",
|
|
89
|
-
"too",
|
|
90
|
-
"very",
|
|
91
|
-
"just",
|
|
92
|
-
"and",
|
|
93
|
-
"but",
|
|
94
|
-
"if",
|
|
95
|
-
"or",
|
|
96
|
-
"because",
|
|
97
|
-
"until",
|
|
98
|
-
"while",
|
|
99
|
-
"which",
|
|
100
|
-
"what",
|
|
101
|
-
"who",
|
|
102
|
-
"whom",
|
|
103
|
-
"this",
|
|
104
|
-
"that",
|
|
105
|
-
"these",
|
|
106
|
-
"those",
|
|
107
|
-
"am",
|
|
108
|
-
"it",
|
|
109
|
-
"its",
|
|
110
|
-
"we",
|
|
111
|
-
"our",
|
|
112
|
-
"you",
|
|
113
|
-
"your",
|
|
114
|
-
"they",
|
|
115
|
-
"their",
|
|
116
|
-
"them",
|
|
117
|
-
"he",
|
|
118
|
-
"him",
|
|
119
|
-
"his",
|
|
120
|
-
"she",
|
|
121
|
-
"her",
|
|
122
|
-
"i",
|
|
123
|
-
"me",
|
|
124
|
-
"my",
|
|
125
|
-
"mine",
|
|
126
|
-
"us",
|
|
127
|
-
"any",
|
|
128
|
-
"both",
|
|
129
|
-
"either",
|
|
130
|
-
"neither",
|
|
131
|
-
"one",
|
|
132
|
-
"two",
|
|
133
|
-
"first",
|
|
134
|
-
"last",
|
|
135
|
-
"another",
|
|
136
|
-
"every",
|
|
137
|
-
"many",
|
|
138
|
-
"much",
|
|
139
|
-
"several",
|
|
140
|
-
"let",
|
|
141
|
-
"new",
|
|
142
|
-
"use",
|
|
143
|
-
"using",
|
|
144
|
-
"make",
|
|
145
|
-
"made",
|
|
146
|
-
"get",
|
|
147
|
-
"got",
|
|
148
|
-
"go",
|
|
149
|
-
"going",
|
|
150
|
-
"want",
|
|
151
|
-
"wanted",
|
|
152
|
-
"like",
|
|
153
|
-
"liked",
|
|
154
|
-
"know",
|
|
155
|
-
"knew",
|
|
156
|
-
"known",
|
|
157
|
-
"think",
|
|
158
|
-
"thought",
|
|
159
|
-
"see",
|
|
160
|
-
"saw",
|
|
161
|
-
"seen",
|
|
162
|
-
"come",
|
|
163
|
-
"came",
|
|
164
|
-
"give",
|
|
165
|
-
"gave",
|
|
166
|
-
"given",
|
|
167
|
-
"take",
|
|
168
|
-
"took",
|
|
169
|
-
"taken",
|
|
170
|
-
"find",
|
|
171
|
-
"found",
|
|
172
|
-
"say",
|
|
173
|
-
"said",
|
|
174
|
-
"tell",
|
|
175
|
-
"told",
|
|
176
|
-
"ask",
|
|
177
|
-
"asked",
|
|
178
|
-
"work",
|
|
179
|
-
"worked",
|
|
180
|
-
"seem",
|
|
181
|
-
"seemed",
|
|
182
|
-
"feel",
|
|
183
|
-
"felt",
|
|
184
|
-
"try",
|
|
185
|
-
"tried",
|
|
186
|
-
"leave",
|
|
187
|
-
"left",
|
|
188
|
-
"call",
|
|
189
|
-
"called",
|
|
190
|
-
"good",
|
|
191
|
-
"well",
|
|
192
|
-
"better",
|
|
193
|
-
"best",
|
|
194
|
-
"bad",
|
|
195
|
-
"worse",
|
|
196
|
-
"worst",
|
|
197
|
-
"old",
|
|
198
|
-
"long",
|
|
199
|
-
"great",
|
|
200
|
-
"little",
|
|
201
|
-
"right",
|
|
202
|
-
"left",
|
|
203
|
-
"big",
|
|
204
|
-
"high",
|
|
205
|
-
"different",
|
|
206
|
-
"important",
|
|
207
|
-
"same",
|
|
208
|
-
"able",
|
|
209
|
-
"next",
|
|
210
|
-
"early",
|
|
211
|
-
"young",
|
|
212
|
-
"public",
|
|
213
|
-
"free",
|
|
214
|
-
"real",
|
|
215
|
-
"easy",
|
|
216
|
-
"clear",
|
|
217
|
-
"recent",
|
|
218
|
-
"local",
|
|
219
|
-
"social",
|
|
220
|
-
"full",
|
|
221
|
-
"small",
|
|
222
|
-
"large",
|
|
223
|
-
"possible",
|
|
224
|
-
"particular",
|
|
225
|
-
"available",
|
|
226
|
-
"special",
|
|
227
|
-
"certain",
|
|
228
|
-
"personal",
|
|
229
|
-
"open",
|
|
230
|
-
"general",
|
|
231
|
-
"enough",
|
|
232
|
-
"probably",
|
|
233
|
-
"actually",
|
|
234
|
-
"especially",
|
|
235
|
-
"finally",
|
|
236
|
-
"usually",
|
|
237
|
-
"perhaps",
|
|
238
|
-
"almost",
|
|
239
|
-
"simply",
|
|
240
|
-
"quickly",
|
|
241
|
-
"recently",
|
|
242
|
-
"already",
|
|
243
|
-
"eventually",
|
|
244
|
-
"suddenly",
|
|
245
|
-
"certainly",
|
|
246
|
-
"definitely",
|
|
247
|
-
"absolutely",
|
|
248
|
-
"completely",
|
|
249
|
-
"totally",
|
|
250
|
-
"entirely",
|
|
251
|
-
"exactly",
|
|
252
|
-
"specifically",
|
|
253
|
-
"particularly",
|
|
254
|
-
"especially",
|
|
255
|
-
"mainly",
|
|
256
|
-
"mostly",
|
|
257
|
-
"partly",
|
|
258
|
-
"fully",
|
|
259
|
-
"nearly",
|
|
260
|
-
"quite",
|
|
261
|
-
"rather",
|
|
262
|
-
"pretty",
|
|
263
|
-
"fairly",
|
|
264
|
-
"really",
|
|
265
|
-
"even",
|
|
266
|
-
"still",
|
|
267
|
-
"yet",
|
|
268
|
-
"ever",
|
|
269
|
-
"never",
|
|
270
|
-
"always",
|
|
271
|
-
"sometimes",
|
|
272
|
-
"often",
|
|
273
|
-
"usually",
|
|
274
|
-
"frequently",
|
|
275
|
-
"rarely",
|
|
276
|
-
"generally",
|
|
277
|
-
"typically",
|
|
278
|
-
"normally",
|
|
279
|
-
"largely",
|
|
280
|
-
"potentially",
|
|
281
|
-
"theoretically",
|
|
282
|
-
"practically",
|
|
283
|
-
"basically",
|
|
284
|
-
"essentially",
|
|
285
|
-
"fundamentally",
|
|
286
|
-
"primarily",
|
|
287
|
-
"chiefly",
|
|
288
|
-
"principally",
|
|
289
|
-
"partially",
|
|
290
|
-
"half",
|
|
291
|
-
"quarter",
|
|
292
|
-
"double",
|
|
293
|
-
"single",
|
|
294
|
-
"multiple",
|
|
295
|
-
"various",
|
|
296
|
-
"hundred",
|
|
297
|
-
"thousand",
|
|
298
|
-
"million",
|
|
299
|
-
"billion",
|
|
300
|
-
]);
|
|
18
|
+
import { STOPWORDS } from "../constants/stopwords.js";
|
|
19
|
+
|
|
20
|
+
/** Warn once per process when Glimpse is unavailable. */
|
|
21
|
+
let _warnedGlimpseUnavailable = false;
|
|
301
22
|
|
|
302
23
|
/** Extract a short title from a question by removing stopwords.
|
|
303
24
|
* Falls back to first 5 words if nothing meaningful remains.
|
|
@@ -325,12 +46,17 @@ function summarizeTitle(question: string, maxWords = 3): string {
|
|
|
325
46
|
|
|
326
47
|
function resolveWebviewHtml(): string {
|
|
327
48
|
const distPath = join(__dirname, "..", "dist", "index.html");
|
|
49
|
+
console.log(`[pi-ask-user-glimpse] Loading webview from: ${distPath}`);
|
|
328
50
|
try {
|
|
329
|
-
|
|
51
|
+
const html = readFileSync(distPath, "utf-8");
|
|
52
|
+
const hasNewCode = html.includes('items-center justify-between');
|
|
53
|
+
console.log(`[pi-ask-user-glimpse] Webview has new code: ${hasNewCode}`);
|
|
54
|
+
return html;
|
|
330
55
|
} catch {
|
|
331
56
|
// Fallback for development: resolve from package root
|
|
332
57
|
const pkgRoot = dirname(_require.resolve("../package.json"));
|
|
333
58
|
const fallbackPath = join(pkgRoot, "dist", "index.html");
|
|
59
|
+
console.log(`[pi-ask-user-glimpse] Fallback: ${fallbackPath}`);
|
|
334
60
|
try {
|
|
335
61
|
return readFileSync(fallbackPath, "utf-8");
|
|
336
62
|
} catch (err) {
|
|
@@ -348,7 +74,11 @@ function resolveWebviewHtml(): string {
|
|
|
348
74
|
export interface AskUserParams {
|
|
349
75
|
question: string;
|
|
350
76
|
context?: string;
|
|
351
|
-
|
|
77
|
+
contextFormat?: "markdown" | "html";
|
|
78
|
+
options?: (
|
|
79
|
+
| string
|
|
80
|
+
| { title: string; description?: string; recommended?: boolean }
|
|
81
|
+
)[];
|
|
352
82
|
questions?: Question[];
|
|
353
83
|
allowMultiple?: boolean;
|
|
354
84
|
allowFreeform?: boolean;
|
|
@@ -356,12 +86,20 @@ export interface AskUserParams {
|
|
|
356
86
|
allowSkip?: boolean;
|
|
357
87
|
displayMode?: string;
|
|
358
88
|
followCursor?: boolean;
|
|
89
|
+
theme?: ThemeMode;
|
|
90
|
+
animationLevel?: AnimationLevel;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface AskUserMetadata {
|
|
94
|
+
theme?: string;
|
|
95
|
+
animationLevel?: string;
|
|
359
96
|
}
|
|
360
97
|
|
|
361
98
|
export async function askUserHandler(
|
|
362
99
|
params: AskUserParams,
|
|
363
100
|
signal: AbortSignal | undefined,
|
|
364
101
|
ctx: ExtensionContext,
|
|
102
|
+
onMetadata?: (metadata: AskUserMetadata) => void,
|
|
365
103
|
) {
|
|
366
104
|
if (signal?.aborted) {
|
|
367
105
|
return {
|
|
@@ -377,7 +115,11 @@ export async function askUserHandler(
|
|
|
377
115
|
|
|
378
116
|
const normalizedOptions = (params.options ?? []).map((opt) => {
|
|
379
117
|
if (typeof opt === "string") return { title: opt };
|
|
380
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
title: opt.title,
|
|
120
|
+
description: opt.description,
|
|
121
|
+
recommended: opt.recommended,
|
|
122
|
+
};
|
|
381
123
|
});
|
|
382
124
|
|
|
383
125
|
const hasOptions = normalizedOptions.length > 0;
|
|
@@ -413,12 +155,16 @@ export async function askUserHandler(
|
|
|
413
155
|
type: payloadType,
|
|
414
156
|
question,
|
|
415
157
|
context,
|
|
158
|
+
contextFormat: params.contextFormat,
|
|
416
159
|
options: normalizedOptions,
|
|
417
160
|
questions: params.questions,
|
|
418
161
|
allowMultiple,
|
|
419
162
|
allowFreeform,
|
|
420
163
|
allowComment,
|
|
421
164
|
allowSkip: params.allowSkip,
|
|
165
|
+
sessionName: ctx.sessionManager.getSessionName(),
|
|
166
|
+
theme: params.theme,
|
|
167
|
+
animationLevel: params.animationLevel,
|
|
422
168
|
};
|
|
423
169
|
|
|
424
170
|
let result: Record<string, unknown> | null = null;
|
|
@@ -435,36 +181,60 @@ export async function askUserHandler(
|
|
|
435
181
|
.replace(/&/g, "\\u0026"),
|
|
436
182
|
);
|
|
437
183
|
|
|
438
|
-
const
|
|
184
|
+
const sessionName = ctx.sessionManager.getSessionName();
|
|
185
|
+
const questionTitle = summarizeTitle(params.question);
|
|
186
|
+
const title = sessionName
|
|
187
|
+
? `Pi · ${sessionName} · ${questionTitle}`
|
|
188
|
+
: `Pi · ${questionTitle}`;
|
|
189
|
+
|
|
190
|
+
const windowOptions: Record<string, unknown> = {
|
|
439
191
|
width: 1200,
|
|
440
192
|
height: 900,
|
|
441
|
-
title:
|
|
193
|
+
title: title.length > 60 ? `${title.slice(0, 57)}…` : title,
|
|
442
194
|
};
|
|
443
195
|
|
|
444
196
|
if (params.followCursor) {
|
|
445
|
-
|
|
197
|
+
windowOptions.followCursor = true;
|
|
446
198
|
}
|
|
447
199
|
|
|
448
|
-
result = (await prompt(html,
|
|
200
|
+
result = (await prompt(html, windowOptions)) as Record<
|
|
449
201
|
string,
|
|
450
202
|
unknown
|
|
451
203
|
> | null;
|
|
452
204
|
if (result === null || result?.__cancelled === true) {
|
|
453
205
|
cancelled = true;
|
|
454
206
|
result = null;
|
|
207
|
+
} else if (result && onMetadata) {
|
|
208
|
+
onMetadata({
|
|
209
|
+
theme: result.__theme as string | undefined,
|
|
210
|
+
animationLevel: result.__animationLevel as string | undefined,
|
|
211
|
+
});
|
|
455
212
|
}
|
|
456
213
|
} catch (err) {
|
|
457
|
-
// Glimpse unavailable —
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
result = fallbackResult;
|
|
214
|
+
// Glimpse unavailable — fast-exit and warn once
|
|
215
|
+
if (!_warnedGlimpseUnavailable) {
|
|
216
|
+
_warnedGlimpseUnavailable = true;
|
|
217
|
+
console.warn(
|
|
218
|
+
"[pi-ask-user-glimpse] Glimpse unavailable — " +
|
|
219
|
+
"ask_user will return errors. " +
|
|
220
|
+
"Install glimpseui or run in a UI-enabled environment.",
|
|
221
|
+
);
|
|
466
222
|
}
|
|
467
|
-
|
|
223
|
+
return {
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: "text" as const,
|
|
227
|
+
text: "No UI available for ask_user dialog. Please ask the user directly in free-form text.",
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
details: {
|
|
231
|
+
question: params.question,
|
|
232
|
+
options: normalizedOptions.map((o) => o.title),
|
|
233
|
+
response: null,
|
|
234
|
+
cancelled: true,
|
|
235
|
+
error: "No UI available",
|
|
236
|
+
},
|
|
237
|
+
};
|
|
468
238
|
}
|
|
469
239
|
|
|
470
240
|
return formatResponse(
|
|
@@ -60,6 +60,7 @@ function buildResponse(
|
|
|
60
60
|
};
|
|
61
61
|
})
|
|
62
62
|
: [],
|
|
63
|
+
additionalComments: pickString(result.additionalComments),
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -79,14 +80,14 @@ function buildResponse(
|
|
|
79
80
|
|
|
80
81
|
function responseToText(response: AskResponse): string {
|
|
81
82
|
if (response.kind === "freeform") {
|
|
82
|
-
return response.text
|
|
83
|
+
return response.text?.trim() || "No response";
|
|
83
84
|
}
|
|
84
85
|
const lines: string[] = [];
|
|
85
86
|
const selections = response.selections ?? [];
|
|
86
87
|
if (selections.length > 0) lines.push(selections.join(", "));
|
|
87
88
|
if (response.comment) lines.push(`Comment: ${response.comment}`);
|
|
88
89
|
if (response.additionalComments) lines.push(`Additional Comments: ${response.additionalComments}`);
|
|
89
|
-
return lines.join("\n\n");
|
|
90
|
+
return lines.join("\n\n") || "No response";
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
export function formatResponse(
|