killeros 2.0.4 → 2.0.6
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 +35 -0
- package/Killeros.ts +2 -0
- package/README.md +20 -12
- package/killeros/commands.ts +1 -2
- package/killeros/goals.ts +179 -51
- package/killeros/hooks.ts +25 -10
- package/killeros/init-evidence.ts +49 -13
- package/killeros/notifications.ts +1 -1
- package/killeros/question.ts +381 -182
- package/killeros/runtime.ts +7 -0
- package/killeros/shell-ui.ts +41 -13
- package/killeros/worked-for.ts +80 -0
- package/package.json +1 -1
package/killeros/question.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
1
2
|
import { type ExtensionAPI, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import {
|
|
3
4
|
decodeKittyPrintable,
|
|
4
5
|
Editor,
|
|
5
6
|
Markdown,
|
|
6
7
|
truncateToWidth,
|
|
8
|
+
visibleWidth,
|
|
7
9
|
wrapTextWithAnsi,
|
|
8
10
|
type EditorTheme,
|
|
9
11
|
} from "@earendil-works/pi-tui";
|
|
@@ -23,8 +25,23 @@ const QuestionParams = Type.Object({
|
|
|
23
25
|
maxItems: 9,
|
|
24
26
|
description: "Between 1 and 9 options for the user to choose from",
|
|
25
27
|
}),
|
|
28
|
+
mode: Type.Optional(StringEnum(["single", "multiple"] as const, {
|
|
29
|
+
description: "Choose one answer or multiple answers; defaults to single",
|
|
30
|
+
})),
|
|
31
|
+
minSelections: Type.Optional(Type.Integer({
|
|
32
|
+
minimum: 1,
|
|
33
|
+
maximum: 10,
|
|
34
|
+
description: "Minimum answers required in multiple mode; defaults to 1",
|
|
35
|
+
})),
|
|
36
|
+
maxSelections: Type.Optional(Type.Integer({
|
|
37
|
+
minimum: 1,
|
|
38
|
+
maximum: 10,
|
|
39
|
+
description: "Maximum answers allowed in multiple mode; defaults to all options plus one custom answer",
|
|
40
|
+
})),
|
|
26
41
|
});
|
|
27
42
|
|
|
43
|
+
type QuestionMode = "single" | "multiple";
|
|
44
|
+
|
|
28
45
|
interface DisplayOption {
|
|
29
46
|
label: string;
|
|
30
47
|
description?: string;
|
|
@@ -33,7 +50,7 @@ interface DisplayOption {
|
|
|
33
50
|
isOther: boolean;
|
|
34
51
|
}
|
|
35
52
|
|
|
36
|
-
interface
|
|
53
|
+
interface SingleQuestionDetails {
|
|
37
54
|
question: string;
|
|
38
55
|
options: string[];
|
|
39
56
|
answer: string | null;
|
|
@@ -42,9 +59,22 @@ interface QuestionDetails {
|
|
|
42
59
|
cancelled?: boolean;
|
|
43
60
|
}
|
|
44
61
|
|
|
62
|
+
interface MultipleQuestionDetails {
|
|
63
|
+
question: string;
|
|
64
|
+
options: string[];
|
|
65
|
+
mode: "multiple";
|
|
66
|
+
answers: string[];
|
|
67
|
+
selectedIndices: number[];
|
|
68
|
+
customAnswer?: string;
|
|
69
|
+
cancelled?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type QuestionDetails = SingleQuestionDetails | MultipleQuestionDetails;
|
|
73
|
+
|
|
45
74
|
type QuestionSelection =
|
|
46
75
|
| { kind: "selected"; answer: string; originalIndex: number }
|
|
47
76
|
| { kind: "custom"; answer: string }
|
|
77
|
+
| { kind: "multiple"; answers: string[]; selectedIndices: number[]; customAnswer?: string }
|
|
48
78
|
| { kind: "cancelled" }
|
|
49
79
|
| { kind: "aborted" };
|
|
50
80
|
|
|
@@ -89,12 +119,71 @@ function oneLine(value: string): string {
|
|
|
89
119
|
return value.replace(/\s+/gu, " ").trim();
|
|
90
120
|
}
|
|
91
121
|
|
|
122
|
+
function boundedRenderLine(value: string, width: number, suffix: string): string {
|
|
123
|
+
return truncateToWidth(value.replace(/\r\n|\r|\n/gu, " "), width, suffix);
|
|
124
|
+
}
|
|
125
|
+
|
|
92
126
|
function visibleOptionRange(total: number, selected: number, capacity: number): { start: number; end: number } {
|
|
93
127
|
const size = Math.max(1, Math.min(total, capacity));
|
|
94
128
|
const start = Math.max(0, Math.min(selected - Math.floor(size / 2), total - size));
|
|
95
129
|
return { start, end: Math.min(total, start + size) };
|
|
96
130
|
}
|
|
97
131
|
|
|
132
|
+
function boundedQuestionLines(question: string, width: number, rowLimit: number): string[] {
|
|
133
|
+
const wrapped = wrapTextWithAnsi(question.replace(/\s+/gu, " ").trim(), width);
|
|
134
|
+
if (wrapped.length <= rowLimit) return wrapped;
|
|
135
|
+
const visible = wrapped.slice(0, rowLimit);
|
|
136
|
+
visible[rowLimit - 1] = truncateToWidth(visible[rowLimit - 1]!, width, "…");
|
|
137
|
+
return visible;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function compactMultipleAnswers(answers: readonly string[], width: number): string {
|
|
141
|
+
const prefix = "✓ ";
|
|
142
|
+
if (answers.length === 0) return truncateToWidth(prefix + "No answers", width, "…");
|
|
143
|
+
const visible: string[] = [];
|
|
144
|
+
for (let index = 0; index < answers.length; index += 1) {
|
|
145
|
+
const remaining = answers.length - index - 1;
|
|
146
|
+
const candidate = [...visible, oneLine(answers[index]!)].join(", ");
|
|
147
|
+
const suffix = remaining > 0 ? `, +${remaining} more` : "";
|
|
148
|
+
if (visibleWidth(prefix + candidate + suffix) > width) break;
|
|
149
|
+
visible.push(oneLine(answers[index]!));
|
|
150
|
+
}
|
|
151
|
+
if (visible.length === answers.length) return prefix + visible.join(", ");
|
|
152
|
+
const hidden = answers.length - visible.length;
|
|
153
|
+
if (visible.length === 0) return truncateToWidth(`${prefix}+${hidden} more`, width, "…");
|
|
154
|
+
return truncateToWidth(`${prefix}${visible.join(", ")}, +${hidden} more`, width, "…");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
class MultipleResultText {
|
|
158
|
+
private readonly answers: readonly string[];
|
|
159
|
+
private readonly expanded: boolean;
|
|
160
|
+
private readonly customAnswer: string | undefined;
|
|
161
|
+
private readonly color: (name: ThemeColor, text: string) => string;
|
|
162
|
+
|
|
163
|
+
constructor(
|
|
164
|
+
answers: readonly string[],
|
|
165
|
+
expanded: boolean,
|
|
166
|
+
customAnswer: string | undefined,
|
|
167
|
+
color: (name: ThemeColor, text: string) => string,
|
|
168
|
+
) {
|
|
169
|
+
this.answers = answers;
|
|
170
|
+
this.expanded = expanded;
|
|
171
|
+
this.customAnswer = customAnswer;
|
|
172
|
+
this.color = color;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
render(width: number): string[] {
|
|
176
|
+
if (width <= 0) return [];
|
|
177
|
+
if (!this.expanded) return [this.color("accent", compactMultipleAnswers(this.answers, width))];
|
|
178
|
+
return this.answers.flatMap((answer) => wrapTextWithAnsi(
|
|
179
|
+
`${this.color("success", "✓ ")}${answer === this.customAnswer ? this.color("muted", "(wrote) ") : ""}${this.color("accent", answer)}`,
|
|
180
|
+
width,
|
|
181
|
+
));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
invalidate(): void {}
|
|
185
|
+
}
|
|
186
|
+
|
|
98
187
|
export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
99
188
|
const customInputHistory: string[] = [];
|
|
100
189
|
let customInputHistoryBytes = 0;
|
|
@@ -130,15 +219,30 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
130
219
|
pi.registerTool<typeof QuestionParams, QuestionDetails>({
|
|
131
220
|
name: "question",
|
|
132
221
|
label: "Question",
|
|
133
|
-
description: `Ask one interactive multiple-choice question. Provide 1-9 concise options. The user can filter options or type a custom answer. Filter queries are limited to ${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()} characters and ${FILTER_QUERY_MAX_BYTES.toLocaleString()} bytes.`,
|
|
222
|
+
description: `Ask one interactive multiple-choice question. Provide 1-9 concise options. Single-select is the default; opt into bounded multi-select with mode "multiple". The user can filter options or type a custom answer. Filter queries are limited to ${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()} characters and ${FILTER_QUERY_MAX_BYTES.toLocaleString()} bytes.`,
|
|
134
223
|
promptSnippet: "Ask the user one multiple-choice question when a decision is required to proceed",
|
|
135
224
|
promptGuidelines: [
|
|
136
225
|
"Use question only when user input is required to choose between concrete alternatives; do not use question for rhetorical or optional follow-up prompts.",
|
|
226
|
+
"Use multiple mode only when the user may need to choose more than one answer; ordinary either/or decisions remain single-select.",
|
|
137
227
|
],
|
|
138
228
|
parameters: QuestionParams,
|
|
139
229
|
executionMode: "sequential",
|
|
140
230
|
|
|
141
231
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
232
|
+
const mode: QuestionMode = params.mode ?? "single";
|
|
233
|
+
const hasSelectionBounds = params.minSelections !== undefined || params.maxSelections !== undefined;
|
|
234
|
+
if (mode === "single" && hasSelectionBounds) {
|
|
235
|
+
throw new Error("Question selection bounds require mode \"multiple\"");
|
|
236
|
+
}
|
|
237
|
+
const maximumAvailable = params.options.length + 1;
|
|
238
|
+
const minSelections = params.minSelections ?? 1;
|
|
239
|
+
const maxSelections = params.maxSelections ?? maximumAvailable;
|
|
240
|
+
if (minSelections > maxSelections) {
|
|
241
|
+
throw new Error("Question minimum selections cannot exceed maximum selections");
|
|
242
|
+
}
|
|
243
|
+
if (maxSelections > maximumAvailable) {
|
|
244
|
+
throw new Error(`Question allows at most ${maximumAvailable} selections including one custom answer`);
|
|
245
|
+
}
|
|
142
246
|
if (ctx.mode !== "tui") throw new Error("The question tool requires interactive TUI mode");
|
|
143
247
|
if (signal?.aborted) throw new Error("Question cancelled before it opened");
|
|
144
248
|
|
|
@@ -150,18 +254,17 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
150
254
|
originalIndex: index + 1,
|
|
151
255
|
isOther: false,
|
|
152
256
|
})),
|
|
153
|
-
{
|
|
154
|
-
label: "Type a custom answer",
|
|
155
|
-
originalIndex: params.options.length + 1,
|
|
156
|
-
isOther: true,
|
|
157
|
-
},
|
|
257
|
+
{ label: "Type a custom answer", originalIndex: params.options.length + 1, isOther: true },
|
|
158
258
|
];
|
|
159
259
|
|
|
160
260
|
let finishFromAbort: (() => void) | undefined;
|
|
161
261
|
const resultPromise = ctx.ui.custom<QuestionSelection>((tui, theme, keybindings, done) => {
|
|
162
262
|
let optionIndex = 0;
|
|
163
|
-
|
|
263
|
+
type EditMode = "none" | "filter" | "custom";
|
|
264
|
+
let editMode: EditMode = "none";
|
|
164
265
|
let filterQuery = "";
|
|
266
|
+
const selectedOriginalIndices = new Set<number>();
|
|
267
|
+
let customAnswer: string | undefined;
|
|
165
268
|
let cachedWidth: number | undefined;
|
|
166
269
|
let cachedRows: number | undefined;
|
|
167
270
|
let cachedLines: string[] | undefined;
|
|
@@ -174,17 +277,11 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
174
277
|
};
|
|
175
278
|
finishFromAbort = () => finish({ kind: "aborted" });
|
|
176
279
|
|
|
177
|
-
const keyHint = (
|
|
178
|
-
keybinding: Parameters<typeof keybindings.getKeys>[0],
|
|
179
|
-
description: string,
|
|
180
|
-
): string => {
|
|
280
|
+
const keyHint = (keybinding: Parameters<typeof keybindings.getKeys>[0], description: string): string => {
|
|
181
281
|
const keyText = keybindings.getKeys(keybinding)
|
|
182
282
|
.join("/")
|
|
183
283
|
.split("/")
|
|
184
|
-
.map((key) => key
|
|
185
|
-
.split("+")
|
|
186
|
-
.map((part) => process.platform === "darwin" && part.toLocaleLowerCase() === "alt" ? "option" : part)
|
|
187
|
-
.join("+"))
|
|
284
|
+
.map((key) => key.split("+").map((part) => process.platform === "darwin" && part.toLocaleLowerCase() === "alt" ? "option" : part).join("+"))
|
|
188
285
|
.join("/");
|
|
189
286
|
return theme.fg("dim", keyText) + theme.fg("muted", ` ${description}`);
|
|
190
287
|
};
|
|
@@ -209,6 +306,16 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
209
306
|
|| option.label.toLocaleLowerCase().includes(query)
|
|
210
307
|
|| option.description?.toLocaleLowerCase().includes(query));
|
|
211
308
|
};
|
|
309
|
+
const selectedCount = (): number => selectedOriginalIndices.size + (customAnswer === undefined ? 0 : 1);
|
|
310
|
+
const orderedMultipleSelection = () => {
|
|
311
|
+
const selectedIndices = [...selectedOriginalIndices].sort((left, right) => left - right);
|
|
312
|
+
const predefined = selectedIndices.map((index) => params.options[index - 1]!.label);
|
|
313
|
+
return {
|
|
314
|
+
answers: customAnswer === undefined ? predefined : [...predefined, customAnswer],
|
|
315
|
+
selectedIndices,
|
|
316
|
+
...(customAnswer === undefined ? {} : { customAnswer }),
|
|
317
|
+
};
|
|
318
|
+
};
|
|
212
319
|
|
|
213
320
|
const invalidate = (): void => {
|
|
214
321
|
cachedWidth = undefined;
|
|
@@ -216,34 +323,83 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
216
323
|
cachedLines = undefined;
|
|
217
324
|
editor.invalidate();
|
|
218
325
|
};
|
|
219
|
-
|
|
220
326
|
const refresh = (): void => {
|
|
221
327
|
invalidate();
|
|
222
328
|
tui.requestRender();
|
|
223
329
|
};
|
|
224
|
-
|
|
330
|
+
const notifyFilterLimit = (): void => ctx.ui.notify(
|
|
331
|
+
`Question filters are limited to ${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()} characters and ${FILTER_QUERY_MAX_BYTES.toLocaleString()} bytes`,
|
|
332
|
+
"error",
|
|
333
|
+
);
|
|
225
334
|
const appendFilterInput = (value: string): void => {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
ctx.ui.notify(
|
|
230
|
-
`Question filters are limited to ${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()} characters and ${FILTER_QUERY_MAX_BYTES.toLocaleString()} bytes`,
|
|
231
|
-
"error",
|
|
232
|
-
);
|
|
335
|
+
const next = filterQuery + value;
|
|
336
|
+
if (inputCharacterCount(next) > FILTER_QUERY_MAX_CHARACTERS || Buffer.byteLength(next, "utf8") > FILTER_QUERY_MAX_BYTES) {
|
|
337
|
+
notifyFilterLimit();
|
|
233
338
|
return;
|
|
234
339
|
}
|
|
235
|
-
filterQuery
|
|
340
|
+
filterQuery = next;
|
|
236
341
|
optionIndex = 0;
|
|
237
342
|
refresh();
|
|
238
343
|
};
|
|
344
|
+
const togglePredefined = (option: DisplayOption): void => {
|
|
345
|
+
if (selectedOriginalIndices.has(option.originalIndex)) {
|
|
346
|
+
selectedOriginalIndices.delete(option.originalIndex);
|
|
347
|
+
refresh();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (selectedCount() >= maxSelections) {
|
|
351
|
+
ctx.ui.notify(`Select at most ${maxSelections} answer${maxSelections === 1 ? "" : "s"}`, "error");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
selectedOriginalIndices.add(option.originalIndex);
|
|
355
|
+
refresh();
|
|
356
|
+
};
|
|
357
|
+
const enterCustomMode = (): void => {
|
|
358
|
+
editMode = "custom";
|
|
359
|
+
editor.setText(mode === "multiple" ? customAnswer ?? "" : "");
|
|
360
|
+
refresh();
|
|
361
|
+
};
|
|
362
|
+
const enterFilterMode = (): void => {
|
|
363
|
+
editMode = "filter";
|
|
364
|
+
editor.setText(filterQuery);
|
|
365
|
+
refresh();
|
|
366
|
+
};
|
|
239
367
|
|
|
240
368
|
editor.onSubmit = (value) => {
|
|
369
|
+
if (editMode === "filter") {
|
|
370
|
+
filterQuery = value;
|
|
371
|
+
optionIndex = 0;
|
|
372
|
+
editMode = "none";
|
|
373
|
+
editor.setText("");
|
|
374
|
+
refresh();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
241
377
|
const answer = value.trim();
|
|
242
378
|
if (answer) {
|
|
243
379
|
if (inputCharacterCount(answer) > CUSTOM_INPUT_MAX_CHARACTERS) {
|
|
244
380
|
ctx.ui.notify(`Custom answers are limited to ${CUSTOM_INPUT_MAX_CHARACTERS} characters`, "error");
|
|
245
381
|
return;
|
|
246
382
|
}
|
|
383
|
+
if (mode === "multiple") {
|
|
384
|
+
const addsSelection = customAnswer === undefined;
|
|
385
|
+
if (addsSelection && selectedCount() >= maxSelections) {
|
|
386
|
+
editor.setText(value);
|
|
387
|
+
ctx.ui.notify(`Select at most ${maxSelections} answer${maxSelections === 1 ? "" : "s"}`, "error");
|
|
388
|
+
refresh();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (!rememberCustomInput(answer)) {
|
|
392
|
+
editor.setText(value);
|
|
393
|
+
ctx.ui.notify(`Custom answer history is limited to ${CUSTOM_INPUT_HISTORY_BYTES} bytes`, "error");
|
|
394
|
+
refresh();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
customAnswer = answer;
|
|
398
|
+
editMode = "none";
|
|
399
|
+
editor.setText("");
|
|
400
|
+
refresh();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
247
403
|
if (!rememberCustomInput(answer)) {
|
|
248
404
|
ctx.ui.notify(`Custom answer history is limited to ${CUSTOM_INPUT_HISTORY_BYTES} bytes`, "error");
|
|
249
405
|
return;
|
|
@@ -251,32 +407,35 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
251
407
|
finish({ kind: "custom", answer });
|
|
252
408
|
return;
|
|
253
409
|
}
|
|
254
|
-
editMode =
|
|
410
|
+
editMode = "none";
|
|
255
411
|
editor.setText("");
|
|
256
412
|
refresh();
|
|
257
413
|
};
|
|
258
414
|
|
|
259
|
-
const
|
|
260
|
-
|
|
415
|
+
const handleEditorInput = (data: string): void => {
|
|
416
|
+
if (keybindings.matches(data, "tui.select.cancel")) {
|
|
417
|
+
editMode = "none";
|
|
418
|
+
editor.setText("");
|
|
419
|
+
refresh();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const before = editor.getExpandedText();
|
|
423
|
+
editor.handleInput(data);
|
|
424
|
+
const after = editor.getExpandedText();
|
|
425
|
+
const overLimit = editMode === "filter"
|
|
426
|
+
? inputCharacterCount(after) > FILTER_QUERY_MAX_CHARACTERS || Buffer.byteLength(after, "utf8") > FILTER_QUERY_MAX_BYTES
|
|
427
|
+
: inputCharacterCount(after) > CUSTOM_INPUT_MAX_CHARACTERS;
|
|
428
|
+
if (overLimit) {
|
|
429
|
+
editor.setText(before);
|
|
430
|
+
if (editMode === "filter") notifyFilterLimit();
|
|
431
|
+
else ctx.ui.notify(`Custom answers are limited to ${CUSTOM_INPUT_MAX_CHARACTERS} characters`, "error");
|
|
432
|
+
}
|
|
261
433
|
refresh();
|
|
262
434
|
};
|
|
263
435
|
|
|
264
436
|
const handleInput = (data: string): void => {
|
|
265
|
-
if (editMode) {
|
|
266
|
-
|
|
267
|
-
editMode = false;
|
|
268
|
-
editor.setText("");
|
|
269
|
-
refresh();
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
const before = editor.getExpandedText();
|
|
273
|
-
editor.handleInput(data);
|
|
274
|
-
const after = editor.getExpandedText();
|
|
275
|
-
if (inputCharacterCount(after) > CUSTOM_INPUT_MAX_CHARACTERS) {
|
|
276
|
-
editor.setText(before);
|
|
277
|
-
ctx.ui.notify(`Custom answers are limited to ${CUSTOM_INPUT_MAX_CHARACTERS} characters`, "error");
|
|
278
|
-
}
|
|
279
|
-
refresh();
|
|
437
|
+
if (editMode !== "none") {
|
|
438
|
+
handleEditorInput(data);
|
|
280
439
|
return;
|
|
281
440
|
}
|
|
282
441
|
|
|
@@ -303,6 +462,53 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
303
462
|
refresh();
|
|
304
463
|
return;
|
|
305
464
|
}
|
|
465
|
+
|
|
466
|
+
const printableInput = decodeQuestionFilterInput(data);
|
|
467
|
+
const isPasteInput = data.includes("\x1B[200~");
|
|
468
|
+
if (mode === "multiple") {
|
|
469
|
+
const selected = visibleOptions[optionIndex];
|
|
470
|
+
if (!isPasteInput && printableInput === " ") {
|
|
471
|
+
if (!selected) return;
|
|
472
|
+
if (selected.isOther) {
|
|
473
|
+
if (customAnswer !== undefined) {
|
|
474
|
+
customAnswer = undefined;
|
|
475
|
+
refresh();
|
|
476
|
+
}
|
|
477
|
+
} else togglePredefined(selected);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (!isPasteInput && printableInput === "/") {
|
|
481
|
+
enterFilterMode();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (!isPasteInput && printableInput && /^[1-9]$/u.test(printableInput)) {
|
|
485
|
+
const numbered = visibleOptions[Number(printableInput) - 1];
|
|
486
|
+
if (!numbered) return;
|
|
487
|
+
if (numbered.isOther) enterCustomMode();
|
|
488
|
+
else togglePredefined(numbered);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (keybindings.matches(data, "tui.select.confirm")) {
|
|
492
|
+
if (selected?.isOther) {
|
|
493
|
+
enterCustomMode();
|
|
494
|
+
} else if (selectedCount() < minSelections) {
|
|
495
|
+
ctx.ui.notify(`Select at least ${minSelections} answer${minSelections === 1 ? "" : "s"}`, "error");
|
|
496
|
+
} else {
|
|
497
|
+
finish({ kind: "multiple", ...orderedMultipleSelection() });
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (keybindings.matches(data, "tui.select.cancel")) {
|
|
502
|
+
if (filterQuery) {
|
|
503
|
+
filterQuery = "";
|
|
504
|
+
optionIndex = 0;
|
|
505
|
+
refresh();
|
|
506
|
+
} else finish({ kind: "cancelled" });
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
306
512
|
if (keybindings.matches(data, "tui.select.confirm")) {
|
|
307
513
|
const selected = visibleOptions[optionIndex];
|
|
308
514
|
if (!selected) return;
|
|
@@ -315,9 +521,7 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
315
521
|
filterQuery = "";
|
|
316
522
|
optionIndex = 0;
|
|
317
523
|
refresh();
|
|
318
|
-
} else {
|
|
319
|
-
finish({ kind: "cancelled" });
|
|
320
|
-
}
|
|
524
|
+
} else finish({ kind: "cancelled" });
|
|
321
525
|
return;
|
|
322
526
|
}
|
|
323
527
|
if (keybindings.matches(data, "tui.editor.deleteCharBackward")) {
|
|
@@ -328,9 +532,7 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
328
532
|
}
|
|
329
533
|
return;
|
|
330
534
|
}
|
|
331
|
-
|
|
332
|
-
const isPasteInput = data.includes("\x1B[200~");
|
|
333
|
-
if (!isPasteInput && printableInput && /^[1-9]$/.test(printableInput)) {
|
|
535
|
+
if (!isPasteInput && printableInput && /^[1-9]$/u.test(printableInput)) {
|
|
334
536
|
const selected = visibleOptions[Number(printableInput) - 1];
|
|
335
537
|
if (!selected) return;
|
|
336
538
|
if (selected.isOther) enterCustomMode();
|
|
@@ -342,134 +544,119 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
342
544
|
|
|
343
545
|
const render = (width: number): string[] => {
|
|
344
546
|
if (width <= 0) return [];
|
|
345
|
-
const renderWidth = width;
|
|
346
547
|
const rowBudget = Math.max(1, tui.terminal.rows);
|
|
347
|
-
if (cachedLines && cachedWidth ===
|
|
548
|
+
if (cachedLines && cachedWidth === width && cachedRows === rowBudget) return cachedLines;
|
|
348
549
|
const visibleOptions = filteredOptions();
|
|
349
550
|
if (optionIndex >= visibleOptions.length) optionIndex = Math.max(0, visibleOptions.length - 1);
|
|
350
551
|
const selected = visibleOptions[optionIndex];
|
|
351
552
|
const position = `Option ${Math.min(optionIndex + 1, visibleOptions.length)}/${visibleOptions.length}`;
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
|
|
553
|
+
const editorText = editor.getExpandedText();
|
|
554
|
+
const editorCount = inputCharacterCount(editorText).toLocaleString();
|
|
555
|
+
const filterCount = inputCharacterCount(editMode === "filter" ? editorText : filterQuery).toLocaleString();
|
|
556
|
+
const compactDraft = editorText.replace(/\r\n|\r|\n/gu, " ↵ ") || (editMode === "filter" ? "Type a filter" : "Type an answer");
|
|
557
|
+
const selectionRange = minSelections === maxSelections ? `${minSelections}` : `${minSelections}–${maxSelections}`;
|
|
558
|
+
const selectionStatus = `Selected ${selectedCount()} · required ${selectionRange}`;
|
|
559
|
+
const optionLabel = (option: DisplayOption, index: number): string => {
|
|
560
|
+
if (mode === "single") return `${index === optionIndex ? ">" : " "} ${index + 1}. ${option.label}`;
|
|
561
|
+
const checked = option.isOther ? customAnswer !== undefined : selectedOriginalIndices.has(option.originalIndex);
|
|
562
|
+
const label = option.isOther && customAnswer !== undefined ? `Custom: ${oneLine(customAnswer)}` : option.label;
|
|
563
|
+
return `${index === optionIndex ? ">" : " "} ${checked ? "[x]" : "[ ]"} ${index + 1}. ${label}`;
|
|
564
|
+
};
|
|
565
|
+
const browseHint = mode === "multiple"
|
|
566
|
+
? `space toggle • / filter • ${keyHint("tui.select.confirm", selected?.isOther ? (customAnswer ? "edit custom" : "add custom") : "submit")} • ${keyHint("tui.select.cancel", filterQuery ? "clear filter" : "cancel")}`
|
|
567
|
+
: `${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", "cancel")}`;
|
|
568
|
+
const editHint = `${keyHint("tui.input.submit", editMode === "filter" ? "apply" : "submit")} • ${keyHint("tui.select.cancel", "options")}`;
|
|
569
|
+
|
|
570
|
+
let lines: string[];
|
|
355
571
|
if (rowBudget <= 2) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()}`
|
|
375
|
-
: position,
|
|
376
|
-
editMode
|
|
377
|
-
? `${keyHint("tui.input.submit", "submit")} • ${keyHint("tui.select.cancel", "options")}`
|
|
378
|
-
: `${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", "cancel")}`,
|
|
572
|
+
if (editMode !== "none") lines = [`${editMode === "filter" ? "Filter" : "Answer"} ${editMode === "filter" ? filterCount : editorCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`, compactDraft];
|
|
573
|
+
else if (mode === "multiple") {
|
|
574
|
+
const focusedRow = selected ? optionLabel(selected, optionIndex) : "";
|
|
575
|
+
const compactSelectionStatus = visibleWidth(selectionStatus) <= width
|
|
576
|
+
? selectionStatus
|
|
577
|
+
: `Selected ${selectedCount()}`;
|
|
578
|
+
lines = focusedRow && visibleWidth(focusedRow) <= width
|
|
579
|
+
? [focusedRow, compactSelectionStatus]
|
|
580
|
+
: [compactSelectionStatus, focusedRow];
|
|
581
|
+
} else lines = [`${selected ? `> ${selected.label}` : "No matching options"} · ${position}`];
|
|
582
|
+
} else if (rowBudget <= 5) {
|
|
583
|
+
lines = [
|
|
584
|
+
...boundedQuestionLines(params.question, width, Math.max(1, rowBudget - 3)),
|
|
585
|
+
editMode !== "none"
|
|
586
|
+
? `${editMode === "filter" ? "Filter" : "Answer"} ${editMode === "filter" ? filterCount : editorCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`
|
|
587
|
+
: selected ? optionLabel(selected, optionIndex) : "No matching options",
|
|
588
|
+
editMode !== "none" ? compactDraft : mode === "multiple" ? selectionStatus : filterQuery ? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()}` : position,
|
|
589
|
+
editMode !== "none" ? editHint : browseHint,
|
|
379
590
|
];
|
|
380
|
-
cachedWidth = renderWidth;
|
|
381
|
-
cachedRows = rowBudget;
|
|
382
|
-
cachedLines = compact.slice(0, rowBudget).map((line) => truncateToWidth(line, renderWidth, "…"));
|
|
383
|
-
return cachedLines;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const contentRows = rowBudget - 5;
|
|
387
|
-
const optionCapacity = Math.max(1, Math.min(5, Math.ceil(contentRows / 2)));
|
|
388
|
-
const detailCapacity = Math.max(0, contentRows - optionCapacity);
|
|
389
|
-
const { start, end } = visibleOptionRange(visibleOptions.length, optionIndex, optionCapacity);
|
|
390
|
-
const hiddenAbove = start > 0 ? `↑ ${start}` : "";
|
|
391
|
-
const hiddenBelowCount = visibleOptions.length - end;
|
|
392
|
-
const hiddenBelow = hiddenBelowCount > 0 ? `↓ ${hiddenBelowCount}` : "";
|
|
393
|
-
const hiddenStatus = [hiddenAbove, hiddenBelow].filter(Boolean).join(" · ");
|
|
394
|
-
const progress = editMode
|
|
395
|
-
? `Answer ${answerCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`
|
|
396
|
-
: filterQuery
|
|
397
|
-
? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()} · ${position}`
|
|
398
|
-
: `${position}${hiddenStatus ? ` · ${hiddenStatus}` : ""}`;
|
|
399
|
-
const hint = editMode
|
|
400
|
-
? `${keyHint("tui.input.submit", "submit")} • ${keyHint("tui.select.cancel", "options")}`
|
|
401
|
-
: `${keyHint("tui.select.up", "up")} • ${keyHint("tui.select.down", "down")} • ${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", filterQuery ? "clear filter" : "cancel")}`;
|
|
402
|
-
const lines: string[] = [
|
|
403
|
-
theme.fg("accent", "─".repeat(renderWidth)),
|
|
404
|
-
theme.fg("text", truncateToWidth(` ${params.question.replace(/\s+/gu, " ")}`, renderWidth, "…")),
|
|
405
|
-
theme.fg("muted", truncateToWidth(` ${progress}`, renderWidth, "…")),
|
|
406
|
-
];
|
|
407
|
-
|
|
408
|
-
if (editMode) {
|
|
409
|
-
const editorLines = editor.render(renderWidth);
|
|
410
|
-
const draftLines = editorLines.length > 2 ? editorLines.slice(1, -1) : editorLines;
|
|
411
|
-
lines.push(...(draftLines.length > 0 ? draftLines : ["Type an answer"]).slice(-contentRows));
|
|
412
591
|
} else {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
italic: (text) => theme.italic(text),
|
|
442
|
-
strikethrough: (text) => theme.strikethrough(text),
|
|
443
|
-
underline: (text) => theme.underline(text),
|
|
444
|
-
},
|
|
445
|
-
{ color: (text) => theme.fg("muted", text) },
|
|
446
|
-
).render(renderWidth));
|
|
447
|
-
}
|
|
448
|
-
if (detailCapacity > 0 && detailLines.length > detailCapacity) {
|
|
449
|
-
const visibleDetailRows = Math.max(0, detailCapacity - 1);
|
|
450
|
-
lines.push(...detailLines.slice(0, visibleDetailRows));
|
|
451
|
-
const hiddenRows = detailLines.length - visibleDetailRows;
|
|
452
|
-
lines.push(theme.fg("dim", `… ${hiddenRows} more line${hiddenRows === 1 ? "" : "s"}`));
|
|
592
|
+
const questionLines = boundedQuestionLines(params.question, width, Math.max(1, rowBudget - 5));
|
|
593
|
+
const contentRows = rowBudget - questionLines.length - 4;
|
|
594
|
+
const optionCapacity = Math.max(1, Math.min(5, Math.ceil(contentRows / 2)));
|
|
595
|
+
const detailCapacity = Math.max(0, contentRows - optionCapacity);
|
|
596
|
+
const { start, end } = visibleOptionRange(visibleOptions.length, optionIndex, optionCapacity);
|
|
597
|
+
const hiddenAbove = start > 0 ? `↑ ${start}` : "";
|
|
598
|
+
const hiddenBelowCount = visibleOptions.length - end;
|
|
599
|
+
const hiddenBelow = hiddenBelowCount > 0 ? `↓ ${hiddenBelowCount}` : "";
|
|
600
|
+
const hiddenStatus = [hiddenAbove, hiddenBelow].filter(Boolean).join(" · ");
|
|
601
|
+
const progress = editMode === "filter"
|
|
602
|
+
? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()}`
|
|
603
|
+
: editMode === "custom"
|
|
604
|
+
? `Answer ${editorCount}/${CUSTOM_INPUT_MAX_CHARACTERS.toLocaleString()}`
|
|
605
|
+
: mode === "multiple"
|
|
606
|
+
? `${selectionStatus}${hiddenStatus ? ` · ${hiddenStatus}` : ""}`
|
|
607
|
+
: filterQuery ? `Filter ${filterCount}/${FILTER_QUERY_MAX_CHARACTERS.toLocaleString()} · ${position}` : `${position}${hiddenStatus ? ` · ${hiddenStatus}` : ""}`;
|
|
608
|
+
const navigationHint = editMode !== "none" ? editHint : mode === "multiple"
|
|
609
|
+
? `${keyHint("tui.select.up", "up")} • ${keyHint("tui.select.down", "down")} • ${browseHint}`
|
|
610
|
+
: `${keyHint("tui.select.up", "up")} • ${keyHint("tui.select.down", "down")} • ${keyHint("tui.select.confirm", "select")} • ${keyHint("tui.select.cancel", filterQuery ? "clear filter" : "cancel")}`;
|
|
611
|
+
lines = [
|
|
612
|
+
theme.fg("accent", "─".repeat(width)),
|
|
613
|
+
...questionLines.map((line) => theme.fg("text", line)),
|
|
614
|
+
theme.fg("muted", truncateToWidth(` ${progress}`, width, "…")),
|
|
615
|
+
];
|
|
616
|
+
if (editMode !== "none") {
|
|
617
|
+
const editorLines = editor.render(width);
|
|
618
|
+
const draftLines = editorLines.length > 2 ? editorLines.slice(1, -1) : editorLines;
|
|
619
|
+
lines.push(...(draftLines.length > 0 ? draftLines : [editMode === "filter" ? "Type a filter" : "Type an answer"]).slice(-contentRows));
|
|
453
620
|
} else {
|
|
454
|
-
|
|
621
|
+
for (let index = start; index < end; index += 1) {
|
|
622
|
+
const option = visibleOptions[index]!;
|
|
623
|
+
const color: ThemeColor = index === optionIndex ? "accent" : "text";
|
|
624
|
+
lines.push(theme.fg(color, truncateToWidth(optionLabel(option, index), width, "…")));
|
|
625
|
+
}
|
|
626
|
+
const detailLines: string[] = [];
|
|
627
|
+
if (selected?.description) detailLines.push(...wrapTextWithAnsi(theme.fg("muted", selected.description), width));
|
|
628
|
+
if (selected?.preview) {
|
|
629
|
+
detailLines.push(theme.fg("accent", theme.bold("Proposal preview")));
|
|
630
|
+
detailLines.push(...new Markdown(selected.preview, 0, 0, {
|
|
631
|
+
heading: (text) => theme.fg("accent", theme.bold(text)), link: (text) => theme.fg("accent", text),
|
|
632
|
+
linkUrl: (text) => theme.fg("dim", text), code: (text) => theme.fg("mdCode", text),
|
|
633
|
+
codeBlock: (text) => theme.fg("mdCodeBlock", text), codeBlockBorder: (text) => theme.fg("mdCodeBlockBorder", text),
|
|
634
|
+
quote: (text) => theme.fg("mdQuote", text), quoteBorder: (text) => theme.fg("mdQuoteBorder", text),
|
|
635
|
+
hr: (text) => theme.fg("mdHr", text), listBullet: (text) => theme.fg("mdListBullet", text),
|
|
636
|
+
bold: (text) => theme.bold(text), italic: (text) => theme.italic(text), strikethrough: (text) => theme.strikethrough(text),
|
|
637
|
+
underline: (text) => theme.underline(text),
|
|
638
|
+
}, { color: (text) => theme.fg("muted", text) }).render(width));
|
|
639
|
+
}
|
|
640
|
+
if (detailCapacity > 0 && detailLines.length > detailCapacity) {
|
|
641
|
+
const visibleDetailRows = Math.max(0, detailCapacity - 1);
|
|
642
|
+
lines.push(...detailLines.slice(0, visibleDetailRows));
|
|
643
|
+
const hiddenRows = detailLines.length - visibleDetailRows;
|
|
644
|
+
lines.push(theme.fg("dim", `… ${hiddenRows} more line${hiddenRows === 1 ? "" : "s"}`));
|
|
645
|
+
} else lines.push(...detailLines.slice(0, detailCapacity));
|
|
455
646
|
}
|
|
647
|
+
lines.push(theme.fg("dim", truncateToWidth(` ${navigationHint}`, width, "…")));
|
|
648
|
+
lines.push(theme.fg("accent", "─".repeat(width)));
|
|
456
649
|
}
|
|
457
|
-
|
|
458
|
-
lines.push(theme.fg("dim", truncateToWidth(` ${hint}`, renderWidth, "…")));
|
|
459
|
-
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
|
|
460
|
-
cachedWidth = renderWidth;
|
|
650
|
+
cachedWidth = width;
|
|
461
651
|
cachedRows = rowBudget;
|
|
462
|
-
cachedLines = lines.slice(0, rowBudget).map((line) =>
|
|
652
|
+
cachedLines = lines.slice(0, rowBudget).map((line) => boundedRenderLine(line, width, rowBudget <= 5 ? "…" : ""));
|
|
463
653
|
return cachedLines;
|
|
464
654
|
};
|
|
465
655
|
|
|
466
656
|
let focused = false;
|
|
467
657
|
return {
|
|
468
658
|
get focused(): boolean { return focused; },
|
|
469
|
-
set focused(value: boolean) {
|
|
470
|
-
focused = value;
|
|
471
|
-
editor.focused = value;
|
|
472
|
-
},
|
|
659
|
+
set focused(value: boolean) { focused = value; editor.focused = value; },
|
|
473
660
|
render,
|
|
474
661
|
handleInput,
|
|
475
662
|
invalidate,
|
|
@@ -488,43 +675,51 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
488
675
|
|
|
489
676
|
const simpleOptions = params.options.map((option) => option.label);
|
|
490
677
|
if (result.kind === "aborted") throw new Error("Question cancelled because the agent operation was aborted");
|
|
491
|
-
if (result.kind === "cancelled") {
|
|
678
|
+
if (result.kind === "cancelled" && mode === "multiple") {
|
|
492
679
|
return {
|
|
493
680
|
content: [{ type: "text", text: "User cancelled the question" }],
|
|
494
|
-
details: { question: params.question, options: simpleOptions,
|
|
681
|
+
details: { question: params.question, options: simpleOptions, mode: "multiple", answers: [], selectedIndices: [], cancelled: true },
|
|
495
682
|
};
|
|
496
683
|
}
|
|
497
|
-
if (result.kind === "
|
|
684
|
+
if (result.kind === "multiple") {
|
|
498
685
|
return {
|
|
499
|
-
content: [{ type: "text", text: `User
|
|
500
|
-
details: {
|
|
686
|
+
content: [{ type: "text", text: `User selected multiple answers:\n${result.answers.map((answer) => `- ${answer}`).join("\n")}` }],
|
|
687
|
+
details: {
|
|
688
|
+
question: params.question, options: simpleOptions, mode: "multiple", answers: result.answers,
|
|
689
|
+
selectedIndices: result.selectedIndices, ...(result.customAnswer === undefined ? {} : { customAnswer: result.customAnswer }),
|
|
690
|
+
},
|
|
501
691
|
};
|
|
502
692
|
}
|
|
693
|
+
if (result.kind === "cancelled") {
|
|
694
|
+
return { content: [{ type: "text", text: "User cancelled the question" }], details: { question: params.question, options: simpleOptions, answer: null, cancelled: true } };
|
|
695
|
+
}
|
|
696
|
+
if (result.kind === "custom") {
|
|
697
|
+
return { content: [{ type: "text", text: `User wrote: ${result.answer}` }], details: { question: params.question, options: simpleOptions, answer: result.answer, wasCustom: true } };
|
|
698
|
+
}
|
|
503
699
|
return {
|
|
504
700
|
content: [{ type: "text", text: `User selected: ${result.answer}` }],
|
|
505
|
-
details: {
|
|
506
|
-
question: params.question,
|
|
507
|
-
options: simpleOptions,
|
|
508
|
-
answer: result.answer,
|
|
509
|
-
selectedIndex: result.originalIndex,
|
|
510
|
-
wasCustom: false,
|
|
511
|
-
},
|
|
701
|
+
details: { question: params.question, options: simpleOptions, answer: result.answer, selectedIndex: result.originalIndex, wasCustom: false },
|
|
512
702
|
};
|
|
513
703
|
},
|
|
514
704
|
|
|
515
705
|
renderCall(args, theme, context) {
|
|
706
|
+
const multiple = args.mode === "multiple";
|
|
707
|
+
const minimum = args.minSelections ?? 1;
|
|
708
|
+
const maximum = args.maxSelections ?? args.options.length + 1;
|
|
516
709
|
if (!context.expanded) {
|
|
517
|
-
const
|
|
518
|
-
|
|
710
|
+
const title = multiple ? "question (multi-select) " : "question ";
|
|
711
|
+
const detail = multiple ? `${args.options.length} options · choose ${minimum}–${maximum}` : `${args.options.length} option${args.options.length === 1 ? "" : "s"}`;
|
|
712
|
+
return new BoundedText(`${theme.fg("toolTitle", theme.bold(title))}${theme.fg("muted", oneLine(args.question))}\n${theme.fg("dim", ` ${detail}`)}`, 3);
|
|
519
713
|
}
|
|
520
|
-
|
|
521
|
-
const lines = [`${theme.fg("toolTitle", theme.bold(
|
|
714
|
+
const title = multiple ? "question (multi-select) " : "question ";
|
|
715
|
+
const lines = [`${theme.fg("toolTitle", theme.bold(title))}${theme.fg("muted", args.question)}`];
|
|
716
|
+
if (multiple) lines.push(theme.fg("dim", `${args.options.length} options · choose ${minimum}–${maximum}`));
|
|
522
717
|
args.options.forEach((option, index) => {
|
|
523
|
-
lines.push(theme.fg("text", `${index + 1}. ${option.label}`));
|
|
718
|
+
lines.push(theme.fg("text", `${multiple ? "[ ] " : ""}${index + 1}. ${option.label}`));
|
|
524
719
|
if (option.description) lines.push(theme.fg("muted", ` ${option.description}`));
|
|
525
720
|
if (option.preview) lines.push(theme.fg("dim", option.preview));
|
|
526
721
|
});
|
|
527
|
-
lines.push(theme.fg("text", `${args.options.length + 1}. Type a custom answer`));
|
|
722
|
+
lines.push(theme.fg("text", `${multiple ? "[ ] " : ""}${args.options.length + 1}. Type a custom answer`));
|
|
528
723
|
return new BoundedText(lines.join("\n"));
|
|
529
724
|
},
|
|
530
725
|
|
|
@@ -534,12 +729,16 @@ export function registerQuestionTool(pi: ExtensionAPI): void {
|
|
|
534
729
|
const first = result.content[0];
|
|
535
730
|
return new BoundedText(first?.type === "text" ? first.text : "", options.expanded ? undefined : 3);
|
|
536
731
|
}
|
|
537
|
-
if (details.cancelled || details.answer === null) return new BoundedText(theme.fg("warning", "Cancelled"));
|
|
732
|
+
if (details.cancelled || ("answer" in details && details.answer === null)) return new BoundedText(theme.fg("warning", "Cancelled"));
|
|
733
|
+
if ("mode" in details && details.mode === "multiple") {
|
|
734
|
+
return new MultipleResultText(details.answers, options.expanded, details.customAnswer, theme.fg.bind(theme));
|
|
735
|
+
}
|
|
736
|
+
if (!("answer" in details) || details.answer === null) return new BoundedText("");
|
|
737
|
+
const answer = details.answer;
|
|
538
738
|
if (details.wasCustom) {
|
|
539
|
-
|
|
540
|
-
return new BoundedText(text, options.expanded ? undefined : 3);
|
|
739
|
+
return new BoundedText(`${theme.fg("success", "✓ ")}${theme.fg("muted", "(wrote) ")}${theme.fg("accent", answer)}`, options.expanded ? undefined : 3);
|
|
541
740
|
}
|
|
542
|
-
return new BoundedText(`${theme.fg("success", "✓ ")}${theme.fg("accent",
|
|
741
|
+
return new BoundedText(`${theme.fg("success", "✓ ")}${theme.fg("accent", answer)}`);
|
|
543
742
|
},
|
|
544
743
|
});
|
|
545
744
|
}
|