@alexleekt/pi-ask-user-glimpse 0.3.2 → 0.4.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.
- package/CHANGELOG.md +62 -0
- package/CONTRIBUTING.md +13 -4
- package/README.md +22 -14
- package/constants/abbreviations.ts +6 -0
- package/constants/stopwords.ts +42 -0
- package/dist/index.html +408 -408
- package/index.ts +155 -48
- package/package.json +4 -2
- package/shared/ask-user.ts +6 -0
- package/tool/ask-user.ts +25 -289
package/index.ts
CHANGED
|
@@ -4,15 +4,36 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
type CustomJournalEntry,
|
|
7
|
-
isCustomEntry,
|
|
7
|
+
isCustomEntry as _isCustomEntry,
|
|
8
8
|
} from "@alexleekt/pi-shared/types";
|
|
9
|
+
|
|
10
|
+
/* ── Defensive: isCustomEntry may resolve to undefined in some jiti cache states ── */
|
|
11
|
+
const isCustomEntry: typeof _isCustomEntry =
|
|
12
|
+
typeof _isCustomEntry === "function"
|
|
13
|
+
? _isCustomEntry
|
|
14
|
+
: (e: unknown): e is CustomJournalEntry => {
|
|
15
|
+
if (!e || typeof e !== "object") return false;
|
|
16
|
+
const entry = e as Record<string, unknown>;
|
|
17
|
+
return (
|
|
18
|
+
entry.type === "custom" &&
|
|
19
|
+
typeof entry.customType === "string" &&
|
|
20
|
+
typeof entry.data === "object" &&
|
|
21
|
+
entry.data !== null
|
|
22
|
+
);
|
|
23
|
+
};
|
|
9
24
|
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
10
25
|
import type {
|
|
11
26
|
BuildSystemPromptOptions,
|
|
12
27
|
ExtensionAPI,
|
|
28
|
+
ExtensionContext,
|
|
13
29
|
} from "@earendil-works/pi-coding-agent";
|
|
14
30
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
15
31
|
import { type AskUserParams, askUserHandler } from "./tool/ask-user.js";
|
|
32
|
+
import type { AnimationLevel, ThemeMode } from "./shared/ask-user.js";
|
|
33
|
+
import { PROTECTED_ABBREVIATIONS } from "./constants/abbreviations.js";
|
|
34
|
+
|
|
35
|
+
/* ── Module-level reference to ExtensionAPI for tool execute closure ── */
|
|
36
|
+
let _pi: ExtensionAPI | undefined;
|
|
16
37
|
|
|
17
38
|
/* ── Generic question-session detection ── */
|
|
18
39
|
|
|
@@ -70,50 +91,142 @@ function getStyleMode(entries: unknown[]): boolean | null {
|
|
|
70
91
|
return typeof enabled === "boolean" ? enabled : null;
|
|
71
92
|
}
|
|
72
93
|
|
|
94
|
+
function getThemeSettings(entries: unknown[]): { theme?: ThemeMode; animationLevel?: AnimationLevel } {
|
|
95
|
+
const entry = entries.find(
|
|
96
|
+
(e): e is CustomJournalEntry =>
|
|
97
|
+
isCustomEntry(e) && e.customType === "ask-user-theme",
|
|
98
|
+
);
|
|
99
|
+
const data = entry?.data as Record<string, unknown> | undefined;
|
|
100
|
+
const theme = typeof data?.theme === "string" ? data.theme : undefined;
|
|
101
|
+
const animationLevel = typeof data?.animationLevel === "string" ? data.animationLevel : undefined;
|
|
102
|
+
return {
|
|
103
|
+
theme: theme === "light" || theme === "dark" || theme === "system" ? theme : undefined,
|
|
104
|
+
animationLevel: animationLevel === "none" || animationLevel === "minimal" || animationLevel === "all" ? animationLevel : undefined,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* ── Shared helpers for consistent ask_user UX across all entry points ── */
|
|
109
|
+
|
|
110
|
+
/** Enrich raw ask_user params with persisted theme/animation settings. */
|
|
111
|
+
function enrichWithThemeSettings(
|
|
112
|
+
params: AskUserParams,
|
|
113
|
+
entries: unknown[],
|
|
114
|
+
): AskUserParams {
|
|
115
|
+
const { theme, animationLevel } = getThemeSettings(entries);
|
|
116
|
+
return { ...params, theme, animationLevel };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Build a metadata saver that writes theme changes back to the session journal. */
|
|
120
|
+
function createThemeSaver(): (metadata: import("./tool/ask-user.js").AskUserMetadata) => void {
|
|
121
|
+
return (metadata) => {
|
|
122
|
+
if ((metadata.theme || metadata.animationLevel) && _pi) {
|
|
123
|
+
_pi.appendEntry("ask-user-theme", {
|
|
124
|
+
theme: metadata.theme,
|
|
125
|
+
animationLevel: metadata.animationLevel,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Execute ask_user with full enrichment + persistence, used by tool and commands alike. */
|
|
132
|
+
async function runAskUserWithTheme(
|
|
133
|
+
rawParams: AskUserParams,
|
|
134
|
+
signal: AbortSignal | undefined,
|
|
135
|
+
ctx: ExtensionContext,
|
|
136
|
+
): Promise<ReturnType<typeof askUserHandler>> {
|
|
137
|
+
const entries = ctx.sessionManager.getEntries();
|
|
138
|
+
const params = enrichWithThemeSettings(rawParams, entries);
|
|
139
|
+
const saveTheme = createThemeSaver();
|
|
140
|
+
let metadata: import("./tool/ask-user.js").AskUserMetadata = {};
|
|
141
|
+
|
|
142
|
+
// Capture the agent's preceding message as additional context
|
|
143
|
+
const preamble = buildAgentPreamble(params, entries);
|
|
144
|
+
const enrichedParams: AskUserParams = preamble
|
|
145
|
+
? {
|
|
146
|
+
...params,
|
|
147
|
+
context: params.context
|
|
148
|
+
? `${preamble}\n\n---\n\n${params.context}`
|
|
149
|
+
: preamble,
|
|
150
|
+
}
|
|
151
|
+
: params;
|
|
152
|
+
|
|
153
|
+
const result = await askUserHandler(enrichedParams, signal, ctx, (m) => {
|
|
154
|
+
metadata = m;
|
|
155
|
+
});
|
|
156
|
+
saveTheme(metadata);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Extract plain text from a Pi journal assistant entry. */
|
|
73
161
|
function extractTextFromAssistantEntry(entry: unknown): string {
|
|
74
|
-
|
|
75
|
-
const
|
|
162
|
+
if (!entry || typeof entry !== "object") return "";
|
|
163
|
+
const e = entry as Record<string, unknown>;
|
|
164
|
+
const message = e.message;
|
|
165
|
+
if (!message || typeof message !== "object") return "";
|
|
166
|
+
const msg = message as Record<string, unknown>;
|
|
167
|
+
|
|
168
|
+
const content = msg.content;
|
|
76
169
|
if (typeof content === "string") return content;
|
|
77
170
|
if (!Array.isArray(content)) return "";
|
|
171
|
+
|
|
78
172
|
return content
|
|
79
173
|
.filter(
|
|
80
174
|
(c): c is { type: string; text: string } =>
|
|
81
|
-
typeof
|
|
82
|
-
|
|
175
|
+
typeof c === "object" &&
|
|
176
|
+
c !== null &&
|
|
177
|
+
typeof (c as Record<string, unknown>).type === "string" &&
|
|
178
|
+
typeof (c as Record<string, unknown>).text === "string",
|
|
83
179
|
)
|
|
84
180
|
.map((c) => c.text)
|
|
85
181
|
.join("\n");
|
|
86
182
|
}
|
|
87
183
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
184
|
+
/** Find the most recent assistant entry in the session journal. */
|
|
185
|
+
function findLastAssistantEntry(entries: unknown[]): unknown | undefined {
|
|
186
|
+
return [...entries].reverse().find((e) => {
|
|
187
|
+
if (!e || typeof e !== "object") return false;
|
|
188
|
+
const entry = e as unknown as Record<string, unknown>;
|
|
189
|
+
const msg = entry.message;
|
|
190
|
+
if (!msg || typeof msg !== "object" || msg === null) return false;
|
|
191
|
+
return (msg as Record<string, unknown>).role === "assistant";
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Extract the agent's introductory text from the most recent assistant
|
|
197
|
+
* message, excluding text that is already duplicated in the question or
|
|
198
|
+
* context fields.
|
|
199
|
+
*/
|
|
200
|
+
function buildAgentPreamble(
|
|
201
|
+
params: AskUserParams,
|
|
202
|
+
entries: unknown[],
|
|
203
|
+
): string | undefined {
|
|
204
|
+
const lastAssistant = findLastAssistantEntry(entries);
|
|
205
|
+
if (!lastAssistant) return undefined;
|
|
206
|
+
|
|
207
|
+
const text = extractTextFromAssistantEntry(lastAssistant).trim();
|
|
208
|
+
if (!text) return undefined;
|
|
209
|
+
|
|
210
|
+
const question = params.question.trim();
|
|
211
|
+
|
|
212
|
+
// Skip if the assistant text is just the question itself
|
|
213
|
+
if (text === question) return undefined;
|
|
214
|
+
|
|
215
|
+
// If the text ends with the question, take only the prefix
|
|
216
|
+
if (text.endsWith(question)) {
|
|
217
|
+
const prefix = text.slice(0, text.length - question.length).trim();
|
|
218
|
+
return prefix || undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Skip if the assistant text is already fully contained in the context
|
|
222
|
+
if (params.context?.trim().includes(text)) return undefined;
|
|
223
|
+
|
|
224
|
+
return text;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ── /ask: extract questions & implicit requests ── */
|
|
228
|
+
|
|
229
|
+
|
|
117
230
|
|
|
118
231
|
function splitSentences(text: string): string[] {
|
|
119
232
|
const PLACEHOLDER = "\x00";
|
|
@@ -171,7 +284,7 @@ function extractQuestions(text: string): string[] {
|
|
|
171
284
|
if (sentence.endsWith("?")) {
|
|
172
285
|
if (hasQuotedQuestion(sentence)) continue;
|
|
173
286
|
if (looksLikeTernary(sentence)) continue;
|
|
174
|
-
if (sentence.length <
|
|
287
|
+
if (sentence.length < 3) continue;
|
|
175
288
|
explicit.push(sentence);
|
|
176
289
|
continue;
|
|
177
290
|
}
|
|
@@ -449,11 +562,12 @@ const askUserTool = defineTool({
|
|
|
449
562
|
}),
|
|
450
563
|
|
|
451
564
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
452
|
-
return
|
|
565
|
+
return runAskUserWithTheme(params, signal, ctx);
|
|
453
566
|
},
|
|
454
567
|
});
|
|
455
568
|
|
|
456
569
|
export default function (pi: ExtensionAPI) {
|
|
570
|
+
_pi = pi;
|
|
457
571
|
pi.registerTool(askUserTool);
|
|
458
572
|
|
|
459
573
|
// ── Auto-detect question sessions and force ask_user usage ──
|
|
@@ -502,26 +616,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
502
616
|
},
|
|
503
617
|
});
|
|
504
618
|
|
|
505
|
-
pi.registerCommand("ask
|
|
619
|
+
pi.registerCommand("ask", {
|
|
506
620
|
description:
|
|
507
621
|
"Extract questions from the last assistant message and ask them via ask_user",
|
|
508
622
|
handler: async (_args, ctx) => {
|
|
509
623
|
if (!ctx.hasUI) {
|
|
510
624
|
console.warn(
|
|
511
|
-
"[pi-ask-user-glimpse] ask
|
|
625
|
+
"[pi-ask-user-glimpse] /ask requires interactive mode",
|
|
512
626
|
);
|
|
513
627
|
return;
|
|
514
628
|
}
|
|
515
629
|
|
|
516
630
|
const entries = ctx.sessionManager.getEntries();
|
|
517
|
-
const lastAssistant =
|
|
518
|
-
const msg = (e as unknown as Record<string, unknown>).message;
|
|
519
|
-
return (
|
|
520
|
-
typeof msg === "object" &&
|
|
521
|
-
msg !== null &&
|
|
522
|
-
(msg as Record<string, unknown>).role === "assistant"
|
|
523
|
-
);
|
|
524
|
-
});
|
|
631
|
+
const lastAssistant = findLastAssistantEntry(entries);
|
|
525
632
|
|
|
526
633
|
if (!lastAssistant) {
|
|
527
634
|
ctx.ui.notify(
|
|
@@ -542,7 +649,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
542
649
|
|
|
543
650
|
const questions = extractQuestions(fullText);
|
|
544
651
|
|
|
545
|
-
const result = await
|
|
652
|
+
const result = await runAskUserWithTheme(
|
|
546
653
|
buildAskLastParams(questions, fullText),
|
|
547
654
|
undefined,
|
|
548
655
|
ctx,
|
|
@@ -591,7 +698,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
591
698
|
const params = buildDebugParams(mode);
|
|
592
699
|
if (!params) return;
|
|
593
700
|
|
|
594
|
-
const result = await
|
|
701
|
+
const result = await runAskUserWithTheme(params, undefined, ctx);
|
|
595
702
|
const textContent = result.content[0];
|
|
596
703
|
const text =
|
|
597
704
|
textContent.type === "text" ? textContent.text : "No response";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexleekt/pi-ask-user-glimpse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Pi extension that replaces ask_user with rich native WebView dialogs via glimpseui and shadcn/ui",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"index.ts",
|
|
33
|
+
"constants",
|
|
33
34
|
"tool",
|
|
34
35
|
"fallback",
|
|
35
36
|
"shared",
|
|
@@ -49,7 +50,8 @@
|
|
|
49
50
|
"prepack": "npm run build",
|
|
50
51
|
"dev:webview": "npx vite --config ./webview/vite.config.ts",
|
|
51
52
|
"validate": "npx tsx scripts/validate.ts",
|
|
52
|
-
"validate:gui": "npx tsx scripts/validate.ts --gui"
|
|
53
|
+
"validate:gui": "npx tsx scripts/validate.ts --gui",
|
|
54
|
+
"test:with-context": "npx tsx scripts/test-with-context.ts"
|
|
53
55
|
},
|
|
54
56
|
"dependencies": {
|
|
55
57
|
"@alexleekt/pi-shared": "^0.1.0",
|
package/shared/ask-user.ts
CHANGED
|
@@ -18,6 +18,9 @@ export interface Question {
|
|
|
18
18
|
allowMultiple?: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export type ThemeMode = "light" | "dark" | "system";
|
|
22
|
+
export type AnimationLevel = "none" | "minimal" | "all";
|
|
23
|
+
|
|
21
24
|
export interface AskUserPayload {
|
|
22
25
|
type: "single-select" | "multi-select" | "questionnaire" | "freeform";
|
|
23
26
|
question: string;
|
|
@@ -28,6 +31,9 @@ export interface AskUserPayload {
|
|
|
28
31
|
allowFreeform: boolean;
|
|
29
32
|
allowComment: boolean;
|
|
30
33
|
allowSkip?: boolean;
|
|
34
|
+
sessionName?: string;
|
|
35
|
+
theme?: ThemeMode;
|
|
36
|
+
animationLevel?: AnimationLevel;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export interface QuestionnaireDetail {
|
package/tool/ask-user.ts
CHANGED
|
@@ -5,299 +5,13 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { prompt } from "glimpseui";
|
|
7
7
|
import { terminalPrompt } from "../fallback/terminal-prompt.js";
|
|
8
|
-
import type { AskUserPayload, Question } from "../shared/ask-user.js";
|
|
8
|
+
import type { AnimationLevel, AskUserPayload, Question, ThemeMode } from "../shared/ask-user.js";
|
|
9
9
|
import { formatResponse } from "./response-formatter.js";
|
|
10
10
|
|
|
11
11
|
const _require = createRequire(import.meta.url);
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
const STOPWORDS = new Set([
|
|
16
|
-
"a",
|
|
17
|
-
"an",
|
|
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
|
-
]);
|
|
14
|
+
import { STOPWORDS } from "../constants/stopwords.js";
|
|
301
15
|
|
|
302
16
|
/** Extract a short title from a question by removing stopwords.
|
|
303
17
|
* Falls back to first 5 words if nothing meaningful remains.
|
|
@@ -356,12 +70,20 @@ export interface AskUserParams {
|
|
|
356
70
|
allowSkip?: boolean;
|
|
357
71
|
displayMode?: string;
|
|
358
72
|
followCursor?: boolean;
|
|
73
|
+
theme?: ThemeMode;
|
|
74
|
+
animationLevel?: AnimationLevel;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface AskUserMetadata {
|
|
78
|
+
theme?: string;
|
|
79
|
+
animationLevel?: string;
|
|
359
80
|
}
|
|
360
81
|
|
|
361
82
|
export async function askUserHandler(
|
|
362
83
|
params: AskUserParams,
|
|
363
84
|
signal: AbortSignal | undefined,
|
|
364
85
|
ctx: ExtensionContext,
|
|
86
|
+
onMetadata?: (metadata: AskUserMetadata) => void,
|
|
365
87
|
) {
|
|
366
88
|
if (signal?.aborted) {
|
|
367
89
|
return {
|
|
@@ -419,6 +141,9 @@ export async function askUserHandler(
|
|
|
419
141
|
allowFreeform,
|
|
420
142
|
allowComment,
|
|
421
143
|
allowSkip: params.allowSkip,
|
|
144
|
+
sessionName: ctx.sessionManager.getSessionName(),
|
|
145
|
+
theme: params.theme,
|
|
146
|
+
animationLevel: params.animationLevel,
|
|
422
147
|
};
|
|
423
148
|
|
|
424
149
|
let result: Record<string, unknown> | null = null;
|
|
@@ -435,10 +160,16 @@ export async function askUserHandler(
|
|
|
435
160
|
.replace(/&/g, "\\u0026"),
|
|
436
161
|
);
|
|
437
162
|
|
|
163
|
+
const sessionName = ctx.sessionManager.getSessionName();
|
|
164
|
+
const questionTitle = summarizeTitle(params.question);
|
|
165
|
+
const title = sessionName
|
|
166
|
+
? `Pi · ${sessionName} · ${questionTitle}`
|
|
167
|
+
: `Pi · ${questionTitle}`;
|
|
168
|
+
|
|
438
169
|
const options: Record<string, unknown> = {
|
|
439
170
|
width: 1200,
|
|
440
171
|
height: 900,
|
|
441
|
-
title:
|
|
172
|
+
title: title.length > 60 ? `${title.slice(0, 57)}…` : title,
|
|
442
173
|
};
|
|
443
174
|
|
|
444
175
|
if (params.followCursor) {
|
|
@@ -452,6 +183,11 @@ export async function askUserHandler(
|
|
|
452
183
|
if (result === null || result?.__cancelled === true) {
|
|
453
184
|
cancelled = true;
|
|
454
185
|
result = null;
|
|
186
|
+
} else if (result && onMetadata) {
|
|
187
|
+
onMetadata({
|
|
188
|
+
theme: result.__theme as string | undefined,
|
|
189
|
+
animationLevel: result.__animationLevel as string | undefined,
|
|
190
|
+
});
|
|
455
191
|
}
|
|
456
192
|
} catch (err) {
|
|
457
193
|
// Glimpse unavailable — fall back to terminal prompt
|