@selesai/code 0.5.12 → 0.5.14
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/README.md +186 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/extensions/caveman/caveman-instructions.cjs +6 -34
- package/dist/extensions/caveman/test/helpers.test.js +7 -13
- package/dist/extensions/package.json +4 -3
- package/dist/extensions/pi-powerline-footer/README.md +1 -1
- package/dist/extensions/pi-powerline-footer/guide.ts +227 -0
- package/dist/extensions/pi-powerline-footer/index.ts +88 -55
- package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -78
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +102 -0
- package/dist/extensions/pi-powerline-footer/welcome.ts +65 -30
- package/dist/extensions/ponytail/index.js +1 -1
- package/dist/extensions/ponytail/ponytail-instructions.cjs +15 -1
- package/dist/extensions/ponytail/test/extension.test.js +20 -4
- package/dist/extensions/ponytail/test/helpers.test.js +32 -0
- package/dist/extensions/workflow/adapter.ts +0 -19
- package/dist/skills/caveman/SKILL.md +1 -0
- package/dist/skills/handoff/SKILL.md +1 -0
- package/dist/skills/handoff-text/SKILL.md +1 -0
- package/dist/skills/implanger/SKILL.md +1 -0
- package/dist/skills/improve-codebase/SKILL.md +1 -0
- package/dist/skills/planger/SKILL.md +1 -0
- package/dist/skills/ponytail/SKILL.md +1 -0
- package/dist/skills/ponytail-audit/SKILL.md +1 -0
- package/dist/skills/ponytail-debt/SKILL.md +1 -0
- package/dist/skills/ponytail-gain/SKILL.md +1 -0
- package/dist/skills/ponytail-help/SKILL.md +1 -0
- package/dist/skills/ponytail-review/SKILL.md +1 -0
- package/dist/skills/workflow-creation/SKILL.md +1 -0
- package/package.json +23 -2
- package/dist/agents/architect.md +0 -217
- package/dist/agents/builder.md +0 -120
- package/dist/agents/commentator.md +0 -130
- package/dist/agents/explorer.md +0 -51
- package/dist/agents/recapper.md +0 -26
- package/dist/extensions/gitignore-guard.ts +0 -132
- package/dist/extensions/hooks/claude-codex-hooks.json +0 -44
- package/dist/extensions/hooks/copilot-hooks.json +0 -21
- package/dist/extensions/hooks/ponytail-activate.js +0 -91
- package/dist/extensions/hooks/ponytail-config.js +0 -122
- package/dist/extensions/hooks/ponytail-instructions.js +0 -94
- package/dist/extensions/hooks/ponytail-mode-tracker.js +0 -55
- package/dist/extensions/hooks/ponytail-runtime.js +0 -68
- package/dist/extensions/hooks/ponytail-statusline.ps1 +0 -21
- package/dist/extensions/hooks/ponytail-statusline.sh +0 -12
- package/dist/extensions/hooks/ponytail-subagent.js +0 -22
- package/dist/extensions/pi-extension/index.js +0 -189
- package/dist/extensions/pi-extension/package.json +0 -8
- package/dist/extensions/pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
- package/dist/extensions/pi-subagents/agents/context-builder.md +0 -46
- package/dist/extensions/pi-subagents/agents/delegate.md +0 -12
- package/dist/extensions/pi-subagents/agents/oracle.md +0 -73
- package/dist/extensions/pi-subagents/agents/planner.md +0 -55
- package/dist/extensions/pi-subagents/agents/reviewer.md +0 -79
- package/dist/extensions/pi-subagents/agents/scout.md +0 -50
- package/dist/extensions/pi-subagents/agents/worker.md +0 -55
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-timing.ts +0 -46
- package/dist/extensions/pi-subagents/test/unit/spawn-timing.test.ts +0 -45
- package/dist/extensions/pi-subagents/tsconfig.runner.json +0 -17
- package/dist/extensions/ponytail-pi-extension/index.js +0 -189
- package/dist/extensions/ponytail-pi-extension/package.json +0 -8
- package/dist/extensions/ponytail-pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/ponytail-pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/prototype.ts +0 -713
- package/dist/extensions/question.ts +0 -350
- package/dist/extensions/quick.ts +0 -630
- package/dist/extensions/tps-tracker.ts +0 -280
- package/dist/extensions/workflow/task-validators.ts +0 -69
- package/dist/skills/selesai-default/SKILL.md +0 -16
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { Type } from "typebox";
|
|
4
|
-
|
|
5
|
-
type RawOption = string | { label: string; description?: string };
|
|
6
|
-
type QuestionAnswer = { answer: string; wasCustom: boolean; index?: number };
|
|
7
|
-
|
|
8
|
-
type QuestionDetails = {
|
|
9
|
-
question: string;
|
|
10
|
-
title?: string;
|
|
11
|
-
options: string[];
|
|
12
|
-
answer: string | null;
|
|
13
|
-
wasCustom?: boolean;
|
|
14
|
-
cancelled?: boolean;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type DisplayOption = { label: string; description?: string; custom?: boolean };
|
|
18
|
-
|
|
19
|
-
const QUESTION_STATUS_KEY = "question";
|
|
20
|
-
const BODY_VIEWPORT_LINES = 18;
|
|
21
|
-
|
|
22
|
-
const OptionSchema = Type.Union([
|
|
23
|
-
Type.String({ description: "Option label" }),
|
|
24
|
-
Type.Object({
|
|
25
|
-
label: Type.String({ description: "Option label shown to the user" }),
|
|
26
|
-
description: Type.Optional(Type.String({ description: "Optional help text shown under the option" })),
|
|
27
|
-
}),
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
const QuestionParams = Type.Object({
|
|
31
|
-
question: Type.String({ description: "Question to ask the user before continuing" }),
|
|
32
|
-
title: Type.Optional(Type.String({ description: "Short title for the question UI" })),
|
|
33
|
-
options: Type.Optional(Type.Array(OptionSchema, { description: "Optional multiple-choice options. Each option may be a string or { label, description }." })),
|
|
34
|
-
allowFreeform: Type.Optional(Type.Boolean({ description: "Allow a custom freeform answer. Defaults to true when no options are provided, false when options exist." })),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
function normalizeOptions(options: RawOption[] | undefined): DisplayOption[] {
|
|
38
|
-
return (options ?? [])
|
|
39
|
-
.map((option) => (typeof option === "string" ? { label: option } : { label: option.label, description: option.description }))
|
|
40
|
-
.filter((option) => option.label.trim().length > 0);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function oneLine(value: unknown, max = 100): string {
|
|
44
|
-
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
45
|
-
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function wrapPlain(value: unknown, width: number): string[] {
|
|
49
|
-
const maxWidth = Math.max(1, width);
|
|
50
|
-
const paragraphs = String(value ?? "").split(/\r?\n/);
|
|
51
|
-
const lines: string[] = [];
|
|
52
|
-
|
|
53
|
-
for (const paragraph of paragraphs) {
|
|
54
|
-
const words = paragraph.trim().split(/\s+/).filter(Boolean);
|
|
55
|
-
if (words.length === 0) {
|
|
56
|
-
lines.push("");
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let current = "";
|
|
61
|
-
for (const word of words) {
|
|
62
|
-
if (!current) {
|
|
63
|
-
current = word;
|
|
64
|
-
} else if (visibleWidth(`${current} ${word}`) <= maxWidth) {
|
|
65
|
-
current += ` ${word}`;
|
|
66
|
-
} else {
|
|
67
|
-
lines.push(...breakLongWord(current, maxWidth));
|
|
68
|
-
current = word;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (current) lines.push(...breakLongWord(current, maxWidth));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return lines.length ? lines : [""];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function breakLongWord(word: string, width: number): string[] {
|
|
78
|
-
if (visibleWidth(word) <= width) return [word];
|
|
79
|
-
const out: string[] = [];
|
|
80
|
-
let line = "";
|
|
81
|
-
for (const char of word) {
|
|
82
|
-
if (line && visibleWidth(line + char) > width) {
|
|
83
|
-
out.push(line);
|
|
84
|
-
line = char;
|
|
85
|
-
} else {
|
|
86
|
-
line += char;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (line) out.push(line);
|
|
90
|
-
return out;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function isPageUp(data: string): boolean {
|
|
94
|
-
return data === "\u001b[5~";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function isPageDown(data: string): boolean {
|
|
98
|
-
return data === "\u001b[6~";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function askQuestion(ctx: any, params: { question: string; title?: string; options?: RawOption[]; allowFreeform?: boolean }, signal?: AbortSignal) {
|
|
102
|
-
const options = normalizeOptions(params.options);
|
|
103
|
-
const allowFreeform = params.allowFreeform ?? options.length === 0;
|
|
104
|
-
if (!allowFreeform && options.length === 0) throw new Error("Question options are empty and allowFreeform is false.");
|
|
105
|
-
|
|
106
|
-
const choices: DisplayOption[] = [...options];
|
|
107
|
-
if (allowFreeform) choices.push({ label: options.length ? "Type custom answer" : "Answer", custom: true });
|
|
108
|
-
|
|
109
|
-
return ctx.ui.custom<QuestionAnswer | null>(
|
|
110
|
-
(tui: any, theme: any, _keybindings: any, done: (value: QuestionAnswer | null) => void) => {
|
|
111
|
-
let selected = 0;
|
|
112
|
-
let editMode = allowFreeform && options.length === 0;
|
|
113
|
-
let scrollOffset = 0;
|
|
114
|
-
let scrollToSelection = true;
|
|
115
|
-
let cachedLines: string[] | undefined;
|
|
116
|
-
let settled = false;
|
|
117
|
-
let removeAbortListener = () => {};
|
|
118
|
-
|
|
119
|
-
function finish(value: QuestionAnswer | null) {
|
|
120
|
-
if (settled) return;
|
|
121
|
-
settled = true;
|
|
122
|
-
removeAbortListener();
|
|
123
|
-
done(value);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (signal) {
|
|
127
|
-
const abort = () => finish(null);
|
|
128
|
-
if (signal.aborted) {
|
|
129
|
-
finish(null);
|
|
130
|
-
} else {
|
|
131
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
132
|
-
removeAbortListener = () => signal.removeEventListener("abort", abort);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const editorTheme: EditorTheme = {
|
|
137
|
-
borderColor: (s) => theme.fg("accent", s),
|
|
138
|
-
selectList: {
|
|
139
|
-
selectedPrefix: (s) => theme.fg("accent", s),
|
|
140
|
-
selectedText: (s) => theme.fg("accent", s),
|
|
141
|
-
description: (s) => theme.fg("muted", s),
|
|
142
|
-
scrollInfo: (s) => theme.fg("dim", s),
|
|
143
|
-
noMatch: (s) => theme.fg("warning", s),
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
const editor = new Editor(tui, editorTheme);
|
|
147
|
-
|
|
148
|
-
editor.onSubmit = (value) => {
|
|
149
|
-
const answer = value.trim();
|
|
150
|
-
if (!answer) return refresh();
|
|
151
|
-
finish({ answer, wasCustom: true });
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
function refresh() {
|
|
155
|
-
cachedLines = undefined;
|
|
156
|
-
tui.requestRender();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function startCustom() {
|
|
160
|
-
editMode = true;
|
|
161
|
-
editor.setText("");
|
|
162
|
-
refresh();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function handleInput(data: string): void {
|
|
166
|
-
if (isPageUp(data)) {
|
|
167
|
-
scrollOffset = Math.max(0, scrollOffset - BODY_VIEWPORT_LINES);
|
|
168
|
-
scrollToSelection = false;
|
|
169
|
-
return refresh();
|
|
170
|
-
}
|
|
171
|
-
if (isPageDown(data)) {
|
|
172
|
-
scrollOffset += BODY_VIEWPORT_LINES;
|
|
173
|
-
scrollToSelection = false;
|
|
174
|
-
return refresh();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (editMode) {
|
|
178
|
-
if (matchesKey(data, Key.escape)) {
|
|
179
|
-
if (options.length === 0) return finish(null);
|
|
180
|
-
editMode = false;
|
|
181
|
-
editor.setText("");
|
|
182
|
-
return refresh();
|
|
183
|
-
}
|
|
184
|
-
editor.handleInput(data);
|
|
185
|
-
return refresh();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (matchesKey(data, Key.escape)) return finish(null);
|
|
189
|
-
if (matchesKey(data, Key.up) || data === "k") {
|
|
190
|
-
selected = Math.max(0, selected - 1);
|
|
191
|
-
scrollToSelection = true;
|
|
192
|
-
return refresh();
|
|
193
|
-
}
|
|
194
|
-
if (matchesKey(data, Key.down) || data === "j") {
|
|
195
|
-
selected = Math.min(choices.length - 1, selected + 1);
|
|
196
|
-
scrollToSelection = true;
|
|
197
|
-
return refresh();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const digit = data >= "1" && data <= "9" ? Number(data) - 1 : -1;
|
|
201
|
-
if (digit >= 0 && digit < choices.length) {
|
|
202
|
-
selected = digit;
|
|
203
|
-
const choice = choices[selected];
|
|
204
|
-
if (choice.custom) return startCustom();
|
|
205
|
-
return finish({ answer: choice.label, wasCustom: false, index: selected + 1 });
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (matchesKey(data, Key.enter)) {
|
|
209
|
-
const choice = choices[selected];
|
|
210
|
-
if (!choice) return;
|
|
211
|
-
if (choice.custom) return startCustom();
|
|
212
|
-
return finish({ answer: choice.label, wasCustom: false, index: selected + 1 });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function render(width: number): string[] {
|
|
217
|
-
if (cachedLines) return cachedLines;
|
|
218
|
-
const lines: string[] = [];
|
|
219
|
-
const body: string[] = [];
|
|
220
|
-
const optionLineStarts: number[] = [];
|
|
221
|
-
const addLine = (target: string[], line = "") => target.push(truncateToWidth(line, width));
|
|
222
|
-
const addBody = (line = "") => addLine(body, line);
|
|
223
|
-
const title = params.title || "Question";
|
|
224
|
-
|
|
225
|
-
addLine(lines, theme.fg("accent", `─ ${title} ${"─".repeat(Math.max(0, width - title.length - 3))}`));
|
|
226
|
-
for (const line of wrapPlain(params.question, Math.max(10, width - 2))) {
|
|
227
|
-
addBody(` ${theme.fg("text", line)}`);
|
|
228
|
-
}
|
|
229
|
-
addBody("");
|
|
230
|
-
|
|
231
|
-
if (options.length > 0) {
|
|
232
|
-
for (let i = 0; i < choices.length; i++) {
|
|
233
|
-
const choice = choices[i];
|
|
234
|
-
const active = i === selected;
|
|
235
|
-
const prefix = active ? theme.fg("accent", "> ") : " ";
|
|
236
|
-
const label = choice.custom && editMode ? `${choice.label} ✎` : choice.label;
|
|
237
|
-
optionLineStarts[i] = body.length;
|
|
238
|
-
const optionColor = active ? "accent" : "text";
|
|
239
|
-
const optionLines = wrapPlain(`${i + 1}. ${label}`, Math.max(10, width - 2));
|
|
240
|
-
optionLines.forEach((optionLine, lineIndex) => {
|
|
241
|
-
addBody((lineIndex === 0 ? prefix : " ") + theme.fg(optionColor, optionLine));
|
|
242
|
-
});
|
|
243
|
-
if (choice.description) {
|
|
244
|
-
for (const descLine of wrapPlain(choice.description, Math.max(10, width - 5))) {
|
|
245
|
-
addBody(` ${theme.fg("muted", descLine)}`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
addBody("");
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const maxScroll = Math.max(0, body.length - BODY_VIEWPORT_LINES);
|
|
253
|
-
if (scrollToSelection && optionLineStarts[selected] !== undefined) {
|
|
254
|
-
const selectedLine = optionLineStarts[selected];
|
|
255
|
-
if (selectedLine < scrollOffset) scrollOffset = selectedLine;
|
|
256
|
-
if (selectedLine >= scrollOffset + BODY_VIEWPORT_LINES) scrollOffset = selectedLine - BODY_VIEWPORT_LINES + 1;
|
|
257
|
-
}
|
|
258
|
-
scrollOffset = Math.min(Math.max(0, scrollOffset), maxScroll);
|
|
259
|
-
|
|
260
|
-
if (scrollOffset > 0) addLine(lines, theme.fg("dim", `↑ ${scrollOffset} line${scrollOffset === 1 ? "" : "s"} above`));
|
|
261
|
-
for (const bodyLine of body.slice(scrollOffset, scrollOffset + BODY_VIEWPORT_LINES)) addLine(lines, bodyLine);
|
|
262
|
-
if (scrollOffset < maxScroll) addLine(lines, theme.fg("dim", `↓ ${maxScroll - scrollOffset} more line${maxScroll - scrollOffset === 1 ? "" : "s"}`));
|
|
263
|
-
|
|
264
|
-
if (editMode) {
|
|
265
|
-
addLine(lines, theme.fg("muted", " Your answer:"));
|
|
266
|
-
for (const line of editor.render(Math.max(20, width - 2))) addLine(lines, ` ${line}`);
|
|
267
|
-
addLine(lines, "");
|
|
268
|
-
addLine(lines, theme.fg("dim", options.length ? " Enter submit • Esc back • PgUp/PgDn scroll" : " Enter submit • Esc cancel • PgUp/PgDn scroll"));
|
|
269
|
-
} else {
|
|
270
|
-
addLine(lines, theme.fg("dim", " ↑↓/j/k move • PgUp/PgDn scroll • 1-9 pick • Enter select • Esc cancel"));
|
|
271
|
-
}
|
|
272
|
-
addLine(lines, theme.fg("accent", "─".repeat(width)));
|
|
273
|
-
|
|
274
|
-
cachedLines = lines;
|
|
275
|
-
return lines;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return { render, handleInput, invalidate: () => (cachedLines = undefined), dispose: removeAbortListener };
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
overlay: true,
|
|
282
|
-
overlayOptions: { anchor: "center", width: "80%", minWidth: 50, maxHeight: "80%", margin: 1 },
|
|
283
|
-
},
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export default function questionExtension(pi: ExtensionAPI) {
|
|
288
|
-
pi.registerTool({
|
|
289
|
-
name: "question",
|
|
290
|
-
label: "Question",
|
|
291
|
-
description: "Ask the user a question in the Pi UI and return the answer. Supports multiple-choice options and freeform answers.",
|
|
292
|
-
promptSnippet: "Ask the user a question when required information is missing.",
|
|
293
|
-
promptGuidelines: [
|
|
294
|
-
"Use question only when you cannot proceed safely or accurately without user input.",
|
|
295
|
-
"Ask concise questions. Prefer options when choices are known.",
|
|
296
|
-
"Set allowFreeform=true when user may need to provide a custom answer.",
|
|
297
|
-
"After the user answers, continue the task immediately using that answer; do not stop and ask the user to type continue unless the task is complete.",
|
|
298
|
-
"Do not use question for information you can infer, inspect, or compute with other tools.",
|
|
299
|
-
],
|
|
300
|
-
parameters: QuestionParams,
|
|
301
|
-
|
|
302
|
-
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
303
|
-
const options = normalizeOptions(params.options as RawOption[] | undefined);
|
|
304
|
-
const detailsBase = { question: params.question, title: params.title, options: options.map((o) => o.label) };
|
|
305
|
-
|
|
306
|
-
if (signal?.aborted) throw new Error("Question cancelled");
|
|
307
|
-
if (!ctx.hasUI) {
|
|
308
|
-
throw new Error("Question tool requires interactive UI; ask user directly or run Pi with UI/RPC support.");
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const title = params.title ? `${params.title}: ` : "";
|
|
312
|
-
ctx.ui.setStatus(QUESTION_STATUS_KEY, ctx.ui.theme.fg("warning", `❓ waiting: ${oneLine(title + params.question, 48)}`));
|
|
313
|
-
ctx.ui.notify(`❓ Question: ${oneLine(title + params.question, 140)}`, "info");
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
const result = await askQuestion(ctx, params as { question: string; title?: string; options?: RawOption[]; allowFreeform?: boolean }, signal);
|
|
317
|
-
if (signal?.aborted) throw new Error("Question cancelled");
|
|
318
|
-
if (!result) {
|
|
319
|
-
ctx.ui.setStatus(QUESTION_STATUS_KEY, ctx.ui.theme.fg("warning", "❓ question cancelled"));
|
|
320
|
-
throw new Error("Question cancelled by user");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const prefix = result.wasCustom ? "User answered" : result.index ? `User selected ${result.index}` : "User selected";
|
|
324
|
-
ctx.ui.setStatus(QUESTION_STATUS_KEY, ctx.ui.theme.fg("success", "✓ question answered"));
|
|
325
|
-
return {
|
|
326
|
-
content: [{ type: "text", text: `${prefix}: ${result.answer}` }],
|
|
327
|
-
details: { ...detailsBase, answer: result.answer, wasCustom: result.wasCustom, cancelled: false } satisfies QuestionDetails,
|
|
328
|
-
};
|
|
329
|
-
} catch (error) {
|
|
330
|
-
ctx.ui.setStatus(QUESTION_STATUS_KEY, ctx.ui.theme.fg("warning", "❓ question failed"));
|
|
331
|
-
throw error;
|
|
332
|
-
}
|
|
333
|
-
},
|
|
334
|
-
|
|
335
|
-
renderCall(args, theme) {
|
|
336
|
-
const title = args.title ? `${args.title}: ` : "";
|
|
337
|
-
const optionCount = Array.isArray(args.options) ? args.options.length : 0;
|
|
338
|
-
const suffix = optionCount ? theme.fg("dim", ` · ${optionCount} option${optionCount === 1 ? "" : "s"}`) : theme.fg("dim", " · freeform");
|
|
339
|
-
return new Text(`${theme.fg("warning", "❓ ")}${theme.fg("toolTitle", theme.bold("QUESTION"))} ${theme.fg("accent", oneLine(title + args.question, 120))}${suffix}`, 0, 0);
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
renderResult(result, _options, theme) {
|
|
343
|
-
const details = result.details as QuestionDetails | undefined;
|
|
344
|
-
if (details?.cancelled) return new Text(theme.fg("warning", "question cancelled"), 0, 0);
|
|
345
|
-
if (details?.answer) return new Text(`${theme.fg("success", "✓ answer")} ${theme.fg("accent", oneLine(details.answer, 120))}`, 0, 0);
|
|
346
|
-
const text = result.content?.find((part: any) => part?.type === "text")?.text ?? "";
|
|
347
|
-
return new Text(text, 0, 0);
|
|
348
|
-
},
|
|
349
|
-
});
|
|
350
|
-
}
|