@selesai/code 0.3.10 → 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/dist/core/agent-session-auto-handoff.test.d.ts +2 -0
- package/dist/core/agent-session-auto-handoff.test.d.ts.map +1 -0
- package/dist/core/agent-session-auto-handoff.test.js +161 -0
- package/dist/core/agent-session-auto-handoff.test.js.map +1 -0
- package/dist/core/agent-session.d.ts +4 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +43 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/settings-manager-auto-handoff.test.d.ts +2 -0
- package/dist/core/settings-manager-auto-handoff.test.d.ts.map +1 -0
- package/dist/core/settings-manager-auto-handoff.test.js +29 -0
- package/dist/core/settings-manager-auto-handoff.test.js.map +1 -0
- package/dist/core/settings-manager.d.ts +9 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +22 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/defaults/models.json +2 -3
- package/dist/extensions/caveman/index.js +16 -1
- package/dist/extensions/caveman/test/extension.test.js +7 -4
- package/dist/extensions/caveman/test/helpers.test.js +12 -1
- package/dist/extensions/context-compaction-reminder.test.ts +82 -0
- package/dist/extensions/context-compaction-reminder.ts +28 -0
- package/dist/extensions/handoff-new.test.ts +2 -13
- package/dist/extensions/handoff-new.ts +16 -25
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-powerline-footer/index.ts +43 -2
- package/dist/extensions/pi-powerline-footer/session-usage.ts +44 -0
- package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +47 -0
- package/dist/extensions/pi-subagents/README.md +1 -1
- package/dist/extensions/pi-subagents/agents/architect.md +9 -29
- package/dist/extensions/pi-subagents/agents/builder.md +17 -106
- package/dist/extensions/pi-subagents/agents/commentator.md +20 -117
- package/dist/extensions/pi-subagents/agents/explorer.md +14 -35
- package/dist/extensions/pi-subagents/agents/recapper.md +20 -12
- package/dist/extensions/pi-subagents/agents/researcher.md +11 -34
- package/dist/extensions/pi-subagents/src/agents/agents.ts +46 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +25 -1
- package/dist/extensions/question/constants.ts +0 -7
- package/dist/extensions/question/index.ts +11 -108
- package/dist/extensions/question/schemas.ts +1 -26
- package/dist/extensions/question/shortcuts.ts +5 -14
- package/dist/extensions/question/tests/question-list.test.ts +8 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +1 -7
- package/dist/extensions/question/tui-adapter.ts +3 -20
- package/dist/extensions/question/types.ts +0 -6
- package/dist/extensions/question/ui-protocol.ts +2 -17
- package/dist/extensions/workflow/adapter.ts +395 -275
- package/dist/extensions/workflow/extension.ts +2 -1
- package/dist/extensions/workflow/modes/prototype.ts +17 -19
- package/dist/extensions/workflow/modes/quick.ts +17 -19
- package/dist/extensions/workflow/modes/task.ts +82 -0
- package/dist/extensions/workflow/run-state.ts +124 -0
- package/dist/extensions/workflow/state-machine.ts +7 -8
- package/dist/extensions/workflow/task-validators.ts +69 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +20 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +8 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +8 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +12 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +14 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +20 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/skills/workflow-creation/SKILL.md +72 -0
- package/docs/workflows.md +46 -7
- package/package.json +1 -1
|
@@ -11,23 +11,21 @@ import {
|
|
|
11
11
|
Key,
|
|
12
12
|
Markdown,
|
|
13
13
|
matchesKey,
|
|
14
|
-
type OverlayHandle,
|
|
15
14
|
Spacer,
|
|
16
15
|
Text,
|
|
17
16
|
type TUI,
|
|
18
17
|
truncateToWidth,
|
|
19
|
-
wrapTextWithAnsi,
|
|
20
18
|
} from "@earendil-works/pi-tui";
|
|
21
19
|
|
|
22
|
-
import { BOX_BORDER_LEFT, BOX_BORDER_OVERHEAD, BOX_BORDER_RIGHT,
|
|
20
|
+
import { BOX_BORDER_LEFT, BOX_BORDER_OVERHEAD, BOX_BORDER_RIGHT, QUESTION_VERSION } from "./constants.ts";
|
|
23
21
|
import { DialogFallback } from "./dialog-adapter.ts";
|
|
24
22
|
import { createFreeformResponse, createSelectionResponse, formatOptionsForMessage, formatResponseSummary, getOptionsFormatError, normalizeOptions, oneLine, safeMarkdownTheme } from "./helpers.ts";
|
|
25
23
|
import { QuestionList } from "./question-list.ts";
|
|
26
24
|
import { MultiSelect, SingleSelect } from "./selection-mode.ts";
|
|
27
25
|
import { resolveShortcuts } from "./shortcuts.ts";
|
|
28
26
|
import { QuestionParamsSchema } from "./schemas.ts";
|
|
29
|
-
import type {
|
|
30
|
-
import {
|
|
27
|
+
import type { QuestionDetails, QuestionMode, QuestionOption, QuestionResponse, RawOption, ResolvedShortcuts } from "./types.ts";
|
|
28
|
+
import { createTUIProtocol } from "./tui-adapter.ts";
|
|
31
29
|
import type { CustomFactory, UIProtocol } from "./ui-protocol.ts";
|
|
32
30
|
|
|
33
31
|
// ---------------------------------------------------------------------------
|
|
@@ -113,7 +111,6 @@ class QuestionComponent extends Container implements Component {
|
|
|
113
111
|
private allowMultiple: boolean,
|
|
114
112
|
private allowFreeform: boolean,
|
|
115
113
|
private allowComment: boolean,
|
|
116
|
-
private displayMode: DisplayMode,
|
|
117
114
|
private tui: TUI,
|
|
118
115
|
private theme: Theme,
|
|
119
116
|
private keybindings: KeybindingsManager,
|
|
@@ -183,14 +180,6 @@ class QuestionComponent extends Container implements Component {
|
|
|
183
180
|
|
|
184
181
|
override render(width: number): string[] {
|
|
185
182
|
const innerWidth = Math.max(1, width - BOX_BORDER_OVERHEAD);
|
|
186
|
-
|
|
187
|
-
if (this.mode === "select" && !this.allowMultiple) {
|
|
188
|
-
const overlayMaxHeight = Math.max(12, Math.floor(this.tui.terminal.rows * OVERLAY_MAX_HEIGHT_RATIO));
|
|
189
|
-
const staticLines = this.countStaticLines(innerWidth);
|
|
190
|
-
const availableOptionRows = Math.max(4, overlayMaxHeight - staticLines);
|
|
191
|
-
this.ensureList().setMaxVisibleRows(availableOptionRows);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
183
|
const rawLines = super.render(innerWidth);
|
|
195
184
|
const borderColor = (s: string) => this.theme.fg("accent", s);
|
|
196
185
|
const titleColor = (s: string) => this.theme.fg("dim", this.theme.bold(s));
|
|
@@ -203,20 +192,6 @@ class QuestionComponent extends Container implements Component {
|
|
|
203
192
|
});
|
|
204
193
|
}
|
|
205
194
|
|
|
206
|
-
private countWrappedLines(text: string, width: number): number {
|
|
207
|
-
return Math.max(1, wrapTextWithAnsi(text, Math.max(10, width - 2)).length);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private countStaticLines(width: number): number {
|
|
211
|
-
const titleLines = 1;
|
|
212
|
-
const questionLines = this.countWrappedLines(this.question, width);
|
|
213
|
-
const contextLines = this.context ? 1 + this.countWrappedLines(this.context, width) : 0;
|
|
214
|
-
const helpLines = 1;
|
|
215
|
-
const borderLines = 2;
|
|
216
|
-
const spacerLines = this.context ? 6 : 5;
|
|
217
|
-
return borderLines + spacerLines + titleLines + questionLines + contextLines + helpLines;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
195
|
private updateStaticText(): void {
|
|
221
196
|
const theme = this.theme;
|
|
222
197
|
const title = this.mode === "comment" ? "Optional comment" : "Question";
|
|
@@ -235,10 +210,6 @@ class QuestionComponent extends Container implements Component {
|
|
|
235
210
|
private updateHelpText(): void {
|
|
236
211
|
const theme = this.theme;
|
|
237
212
|
const kb = this.keybindings;
|
|
238
|
-
const overlayHint =
|
|
239
|
-
this.displayMode === "overlay" && !this.shortcuts.overlayToggle.disabled
|
|
240
|
-
? `${theme.fg("dim", this.shortcuts.overlayToggle.spec)}${theme.fg("muted", " hide")}`
|
|
241
|
-
: null;
|
|
242
213
|
const commentHint =
|
|
243
214
|
this.allowComment && !this.shortcuts.commentToggle.disabled
|
|
244
215
|
? `${theme.fg("dim", this.shortcuts.commentToggle.spec)}${theme.fg("muted", " toggle context")}`
|
|
@@ -249,7 +220,6 @@ class QuestionComponent extends Container implements Component {
|
|
|
249
220
|
`${theme.fg("dim", kb.getKeys("tui.input.submit").join("/"))}${theme.fg("muted", this.mode === "comment" ? " submit/skip" : " submit")}`,
|
|
250
221
|
`${theme.fg("dim", kb.getKeys("tui.input.newLine").join("/"))}${theme.fg("muted", " newline")}`,
|
|
251
222
|
`${theme.fg("dim", "esc")}${theme.fg("muted", " back")}`,
|
|
252
|
-
overlayHint,
|
|
253
223
|
]
|
|
254
224
|
.filter((h): h is string => !!h)
|
|
255
225
|
.join(" • ");
|
|
@@ -262,7 +232,6 @@ class QuestionComponent extends Container implements Component {
|
|
|
262
232
|
`${theme.fg("dim", "↑↓")}${theme.fg("muted", " navigate")}`,
|
|
263
233
|
`${theme.fg("dim", "space")}${theme.fg("muted", " toggle")}`,
|
|
264
234
|
commentHint,
|
|
265
|
-
overlayHint,
|
|
266
235
|
`${theme.fg("dim", kb.getKeys("tui.select.confirm").join("/"))}${theme.fg("muted", " submit")}`,
|
|
267
236
|
`${theme.fg("dim", kb.getKeys("tui.select.cancel").join("/"))}${theme.fg("muted", " cancel")}`,
|
|
268
237
|
]
|
|
@@ -275,7 +244,6 @@ class QuestionComponent extends Container implements Component {
|
|
|
275
244
|
`${theme.fg("dim", kb.getKeys("tui.editor.deleteCharBackward").join("/"))}${theme.fg("muted", " erase")}`,
|
|
276
245
|
`${theme.fg("dim", "↑↓")}${theme.fg("muted", " navigate")}`,
|
|
277
246
|
commentHint,
|
|
278
|
-
overlayHint,
|
|
279
247
|
`${theme.fg("dim", kb.getKeys("tui.select.confirm").join("/"))}${theme.fg("muted", " select")}`,
|
|
280
248
|
`${theme.fg("dim", "esc")}${theme.fg("muted", " clear/cancel")}`,
|
|
281
249
|
]
|
|
@@ -433,13 +401,7 @@ export default function questionExtension(pi: ExtensionAPI) {
|
|
|
433
401
|
const allowComment = params.allowComment ?? false;
|
|
434
402
|
const timeout = params.timeout as number | undefined;
|
|
435
403
|
|
|
436
|
-
const envMode = process.env.PI_QUESTION_DISPLAY_MODE;
|
|
437
|
-
const envDisplayMode: DisplayMode | undefined = envMode === "overlay" || envMode === "inline" ? envMode : undefined;
|
|
438
|
-
const effectiveDisplayMode: DisplayMode = (params.displayMode as DisplayMode | undefined) ?? envDisplayMode ?? "overlay";
|
|
439
|
-
|
|
440
404
|
const shortcuts: ResolvedShortcuts = resolveShortcuts(
|
|
441
|
-
params.overlayToggleKey as string | null | undefined,
|
|
442
|
-
process.env.PI_QUESTION_OVERLAY_TOGGLE_KEY,
|
|
443
405
|
params.commentToggleKey as string | null | undefined,
|
|
444
406
|
process.env.PI_QUESTION_COMMENT_TOGGLE_KEY,
|
|
445
407
|
);
|
|
@@ -473,42 +435,12 @@ export default function questionExtension(pi: ExtensionAPI) {
|
|
|
473
435
|
};
|
|
474
436
|
}
|
|
475
437
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (options.length === 0) {
|
|
483
|
-
if (!allowFreeform) {
|
|
484
|
-
protocol.setStatus(QUESTION_STATUS_KEY, protocol.theme.fg("error", "question failed"));
|
|
485
|
-
return {
|
|
486
|
-
content: [{ type: "text", text: "Question options are empty and allowFreeform is false." }],
|
|
487
|
-
isError: true,
|
|
488
|
-
details: { ...detailsBase, response: null, cancelled: true } satisfies QuestionDetails,
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
try {
|
|
492
|
-
const prompt = normalizedContext ? `${rawQuestion}\n\nContext:\n${normalizedContext}` : rawQuestion;
|
|
493
|
-
const answer = await protocol.input(prompt, "Type your answer...", timeout ? { timeout } : undefined);
|
|
494
|
-
const response = createFreeformResponse(answer);
|
|
495
|
-
if (!response) {
|
|
496
|
-
protocol.setStatus(QUESTION_STATUS_KEY, protocol.theme.fg("warning", "question cancelled"));
|
|
497
|
-
return {
|
|
498
|
-
content: [{ type: "text", text: "User cancelled the question" }],
|
|
499
|
-
details: { ...detailsBase, response: null, cancelled: true } satisfies QuestionDetails,
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
pi.events.emit("question:answered", { question: rawQuestion, context: normalizedContext, response });
|
|
503
|
-
protocol.setStatus(QUESTION_STATUS_KEY, protocol.theme.fg("success", "question answered"));
|
|
504
|
-
return {
|
|
505
|
-
content: [{ type: "text", text: `User answered: ${formatResponseSummary(response)}` }],
|
|
506
|
-
details: { ...detailsBase, response, cancelled: false } satisfies QuestionDetails,
|
|
507
|
-
};
|
|
508
|
-
} catch (error) {
|
|
509
|
-
protocol.setStatus(QUESTION_STATUS_KEY, protocol.theme.fg("error", "question failed"));
|
|
510
|
-
throw error;
|
|
511
|
-
}
|
|
438
|
+
if (options.length === 0 && !allowFreeform) {
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: "text", text: "Question options are empty and allowFreeform is false." }],
|
|
441
|
+
isError: true,
|
|
442
|
+
details: { ...detailsBase, response: null, cancelled: true } satisfies QuestionDetails,
|
|
443
|
+
};
|
|
512
444
|
}
|
|
513
445
|
|
|
514
446
|
onUpdate?.({
|
|
@@ -517,9 +449,6 @@ export default function questionExtension(pi: ExtensionAPI) {
|
|
|
517
449
|
});
|
|
518
450
|
|
|
519
451
|
let result: QuestionResponse | null;
|
|
520
|
-
let overlayHandle: OverlayHandle | undefined;
|
|
521
|
-
let removeOverlayInputListener: (() => void) | undefined;
|
|
522
|
-
let hasAnnouncedHide = false;
|
|
523
452
|
|
|
524
453
|
try {
|
|
525
454
|
const customFactory: CustomFactory = (
|
|
@@ -541,7 +470,6 @@ export default function questionExtension(pi: ExtensionAPI) {
|
|
|
541
470
|
allowMultiple,
|
|
542
471
|
allowFreeform,
|
|
543
472
|
allowComment,
|
|
544
|
-
effectiveDisplayMode,
|
|
545
473
|
tui,
|
|
546
474
|
theme,
|
|
547
475
|
keybindings,
|
|
@@ -550,51 +478,27 @@ export default function questionExtension(pi: ExtensionAPI) {
|
|
|
550
478
|
);
|
|
551
479
|
};
|
|
552
480
|
|
|
553
|
-
const
|
|
554
|
-
if (
|
|
555
|
-
effectiveDisplayMode === "overlay" &&
|
|
556
|
-
!overlayToggle.disabled &&
|
|
557
|
-
typeof protocol.onTerminalInput === "function"
|
|
558
|
-
) {
|
|
559
|
-
removeOverlayInputListener = protocol.onTerminalInput((data) => {
|
|
560
|
-
if (!overlayToggle.matches(data) || !overlayHandle) return undefined;
|
|
561
|
-
const nextHidden = !overlayHandle.isHidden();
|
|
562
|
-
overlayHandle.setHidden(nextHidden);
|
|
563
|
-
if (nextHidden && !hasAnnouncedHide) {
|
|
564
|
-
hasAnnouncedHide = true;
|
|
565
|
-
protocol.notify?.(`question hidden — press ${overlayToggle.spec} to reopen`, "info");
|
|
566
|
-
}
|
|
567
|
-
return { consume: true };
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
const customResult = await protocol.custom(customFactory, buildCustomUIOptions(effectiveDisplayMode, (handle) => {
|
|
572
|
-
overlayHandle = handle;
|
|
573
|
-
}));
|
|
481
|
+
const customResult = await protocol.custom(customFactory);
|
|
574
482
|
|
|
575
483
|
if (customResult !== undefined) {
|
|
576
484
|
result = customResult;
|
|
577
485
|
} else {
|
|
578
486
|
// RPC/headless: custom() returns undefined — degrade to dialog protocol.
|
|
579
487
|
result = await DialogFallback.ask(
|
|
580
|
-
{ question: rawQuestion, context: normalizedContext, options, allowMultiple, allowFreeform, allowComment,
|
|
488
|
+
{ question: rawQuestion, context: normalizedContext, options, allowMultiple, allowFreeform, allowComment, shortcuts, timeout },
|
|
581
489
|
protocol,
|
|
582
490
|
);
|
|
583
491
|
}
|
|
584
492
|
} catch (error) {
|
|
585
493
|
const message = error instanceof Error ? `${error.message}\n${error.stack ?? ""}` : String(error);
|
|
586
|
-
protocol.setStatus(QUESTION_STATUS_KEY, protocol.theme.fg("error", "question failed"));
|
|
587
494
|
return {
|
|
588
495
|
content: [{ type: "text", text: `Question tool failed: ${message}` }],
|
|
589
496
|
isError: true,
|
|
590
497
|
details: { error: message } as unknown as QuestionDetails,
|
|
591
498
|
};
|
|
592
|
-
} finally {
|
|
593
|
-
removeOverlayInputListener?.();
|
|
594
499
|
}
|
|
595
500
|
|
|
596
501
|
if (result === null) {
|
|
597
|
-
protocol.setStatus(QUESTION_STATUS_KEY, protocol.theme.fg("warning", "question cancelled"));
|
|
598
502
|
pi.events.emit("question:cancelled", { question: rawQuestion, context: normalizedContext, options });
|
|
599
503
|
return {
|
|
600
504
|
content: [{ type: "text", text: "User cancelled the question" }],
|
|
@@ -602,7 +506,6 @@ export default function questionExtension(pi: ExtensionAPI) {
|
|
|
602
506
|
};
|
|
603
507
|
}
|
|
604
508
|
|
|
605
|
-
protocol.setStatus(QUESTION_STATUS_KEY, protocol.theme.fg("success", "question answered"));
|
|
606
509
|
pi.events.emit("question:answered", { question: rawQuestion, context: normalizedContext, response: result });
|
|
607
510
|
return {
|
|
608
511
|
content: [{ type: "text", text: `User answered: ${formatResponseSummary(result)}` }],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Typebox schemas for the question tool parameters.
|
|
2
2
|
|
|
3
|
-
import { Type
|
|
3
|
+
import { Type } from "typebox";
|
|
4
4
|
|
|
5
5
|
const OptionSchema = Type.Union([
|
|
6
6
|
Type.String({ description: "Option label" }),
|
|
@@ -10,19 +10,6 @@ const OptionSchema = Type.Union([
|
|
|
10
10
|
}),
|
|
11
11
|
]);
|
|
12
12
|
|
|
13
|
-
/** Flat { type: "string", enum: [...] } instead of anyOf — Google function-calling rejects unions. */
|
|
14
|
-
export function StringEnum<const T extends readonly string[]>(
|
|
15
|
-
values: T,
|
|
16
|
-
options?: { description?: string; default?: T[number] },
|
|
17
|
-
): TUnsafe<T[number]> {
|
|
18
|
-
return Type.Unsafe<T[number]>({
|
|
19
|
-
type: "string",
|
|
20
|
-
enum: [...values],
|
|
21
|
-
...(options?.description ? { description: options.description } : {}),
|
|
22
|
-
...(options?.default !== undefined ? { default: options.default } : {}),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
13
|
export const QuestionParamsSchema = Type.Object({
|
|
27
14
|
question: Type.String({ description: "Question to ask the user before continuing" }),
|
|
28
15
|
title: Type.Optional(Type.String({ description: "Short title for the question UI (deprecated alias of context summary)" })),
|
|
@@ -45,18 +32,6 @@ export const QuestionParamsSchema = Type.Object({
|
|
|
45
32
|
allowComment: Type.Optional(
|
|
46
33
|
Type.Boolean({ description: "Collect an optional comment after selecting one or more options. Default: false" }),
|
|
47
34
|
),
|
|
48
|
-
displayMode: Type.Optional(
|
|
49
|
-
StringEnum(["overlay", "inline"] as const, {
|
|
50
|
-
description:
|
|
51
|
-
"UI rendering mode. 'overlay' shows a centered modal, 'inline' renders in-place. Default: PI_QUESTION_DISPLAY_MODE env var if set, otherwise 'overlay'.",
|
|
52
|
-
}),
|
|
53
|
-
),
|
|
54
|
-
overlayToggleKey: Type.Optional(
|
|
55
|
-
Type.String({
|
|
56
|
-
description:
|
|
57
|
-
"Shortcut for hiding/showing the overlay popup (overlay mode only), e.g. 'alt+o'. Pass 'off' to disable. Default: PI_QUESTION_OVERLAY_TOGGLE_KEY env var if set, otherwise 'alt+o'.",
|
|
58
|
-
}),
|
|
59
|
-
),
|
|
60
35
|
commentToggleKey: Type.Optional(
|
|
61
36
|
Type.String({
|
|
62
37
|
description:
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// Shortcut resolution for
|
|
1
|
+
// Shortcut resolution for the optional-comment toggle key.
|
|
2
2
|
|
|
3
3
|
import { matchesKey } from "@earendil-works/pi-tui";
|
|
4
4
|
|
|
5
|
-
import { DISABLED_SHORTCUT, SHORTCUT_DISABLE_VALUES } from "./constants.ts";
|
|
5
|
+
import { DEFAULT_COMMENT_TOGGLE_KEY, DISABLED_SHORTCUT, SHORTCUT_DISABLE_VALUES } from "./constants.ts";
|
|
6
6
|
import type { ResolvedShortcut, ResolvedShortcuts } from "./types.ts";
|
|
7
|
-
import { DEFAULT_COMMENT_TOGGLE_KEY, DEFAULT_OVERLAY_TOGGLE_KEY } from "./constants.ts";
|
|
8
7
|
|
|
9
8
|
export function normalizeShortcutSpec(value: string | null | undefined): string | null | undefined {
|
|
10
9
|
if (value === undefined) return undefined;
|
|
@@ -40,14 +39,6 @@ export function resolveShortcut(
|
|
|
40
39
|
return DISABLED_SHORTCUT;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
export function resolveShortcuts(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
commentToggleKey: string | null | undefined,
|
|
47
|
-
commentEnv: string | undefined,
|
|
48
|
-
): ResolvedShortcuts {
|
|
49
|
-
return {
|
|
50
|
-
overlayToggle: resolveShortcut(overlayToggleKey, overlayEnv, DEFAULT_OVERLAY_TOGGLE_KEY),
|
|
51
|
-
commentToggle: resolveShortcut(commentToggleKey, commentEnv, DEFAULT_COMMENT_TOGGLE_KEY),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
42
|
+
export function resolveShortcuts(commentToggleKey: string | null | undefined, commentEnv: string | undefined): ResolvedShortcuts {
|
|
43
|
+
return { commentToggle: resolveShortcut(commentToggleKey, commentEnv, DEFAULT_COMMENT_TOGGLE_KEY) };
|
|
44
|
+
}
|
|
@@ -86,6 +86,14 @@ test("SingleSelect: confirm on freeform row → onEnterFreeform", () => {
|
|
|
86
86
|
assert.equal(called, true);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
test("SingleSelect: no options with freeform → opens custom response", () => {
|
|
90
|
+
const list = new QuestionList([], SingleSelect, true, false, fakeTheme, fakeKeybindings(["tui.select.confirm"]), disabledShortcut);
|
|
91
|
+
let called = false;
|
|
92
|
+
list.onEnterFreeform = () => (called = true);
|
|
93
|
+
list.handleInput(ENTER);
|
|
94
|
+
assert.equal(called, true);
|
|
95
|
+
});
|
|
96
|
+
|
|
89
97
|
test("SingleSelect: confirm on comment-toggle → toggles comment, no submit", () => {
|
|
90
98
|
const list = new QuestionList(opts(["a"]), SingleSelect, false, true, fakeTheme, fakeKeybindings(["tui.select.confirm"]), disabledShortcut);
|
|
91
99
|
let submitted = false;
|
|
@@ -23,8 +23,6 @@ function fakeProtocol(overrides: Partial<UIProtocol> = {}): UIProtocol {
|
|
|
23
23
|
calls.push({ method: "input", args: [prompt, placeholder, opts] });
|
|
24
24
|
return overrides.input?.(prompt, placeholder, opts);
|
|
25
25
|
},
|
|
26
|
-
setStatus: () => {},
|
|
27
|
-
notify: () => {},
|
|
28
26
|
...overrides,
|
|
29
27
|
} as UIProtocol;
|
|
30
28
|
}
|
|
@@ -37,11 +35,7 @@ function makeParams(overrides: Partial<ResolvedQuestionParams> = {}): ResolvedQu
|
|
|
37
35
|
allowMultiple: false,
|
|
38
36
|
allowFreeform: false,
|
|
39
37
|
allowComment: false,
|
|
40
|
-
|
|
41
|
-
shortcuts: {
|
|
42
|
-
overlayToggle: { disabled: true, spec: null, matches: () => false },
|
|
43
|
-
commentToggle: { disabled: true, spec: null, matches: () => false },
|
|
44
|
-
},
|
|
38
|
+
shortcuts: { commentToggle: { disabled: true, spec: null, matches: () => false } },
|
|
45
39
|
timeout: undefined,
|
|
46
40
|
...overrides,
|
|
47
41
|
};
|
|
@@ -1,33 +1,16 @@
|
|
|
1
1
|
// TUI protocol adapter — wraps ctx.ui into UIProtocol.
|
|
2
2
|
|
|
3
3
|
import type { ExtensionContext } from "@selesai/code";
|
|
4
|
-
import type { OverlayHandle } from "@earendil-works/pi-tui";
|
|
5
4
|
|
|
6
|
-
import {
|
|
7
|
-
import type {
|
|
8
|
-
import type { CustomUIOptions, UIProtocol } from "./ui-protocol.ts";
|
|
9
|
-
|
|
10
|
-
export function buildCustomUIOptions(
|
|
11
|
-
displayMode: DisplayMode,
|
|
12
|
-
onHandle?: (handle: OverlayHandle) => void,
|
|
13
|
-
): CustomUIOptions | undefined {
|
|
14
|
-
if (displayMode === "inline") return undefined;
|
|
15
|
-
return {
|
|
16
|
-
overlay: true,
|
|
17
|
-
overlayOptions: { anchor: "center", width: OVERLAY_WIDTH, minWidth: OVERLAY_MIN_WIDTH, maxHeight: OVERLAY_OVERLAY_MAX_HEIGHT, margin: 1 },
|
|
18
|
-
...(onHandle ? { onHandle } : {}),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
5
|
+
import type { QuestionResponse } from "./types.ts";
|
|
6
|
+
import type { UIProtocol } from "./ui-protocol.ts";
|
|
21
7
|
|
|
22
8
|
export function createTUIProtocol(ctx: ExtensionContext): UIProtocol {
|
|
23
9
|
return {
|
|
24
10
|
hasUI: ctx.hasUI,
|
|
25
11
|
theme: ctx.ui.theme,
|
|
26
|
-
custom: (factory
|
|
12
|
+
custom: (factory) => ctx.ui.custom(factory as any) as Promise<QuestionResponse | null | undefined>,
|
|
27
13
|
select: (prompt, options, opts) => ctx.ui.select(prompt, options, opts),
|
|
28
14
|
input: (prompt, placeholder, opts) => ctx.ui.input(prompt, placeholder, opts),
|
|
29
|
-
onTerminalInput: typeof ctx.ui.onTerminalInput === "function" ? ctx.ui.onTerminalInput.bind(ctx.ui) : undefined,
|
|
30
|
-
setStatus: (key, text) => ctx.ui.setStatus(key, text),
|
|
31
|
-
notify: (msg, level) => ctx.ui.notify(msg, level),
|
|
32
15
|
};
|
|
33
16
|
}
|
|
@@ -21,8 +21,6 @@ export interface QuestionDetails {
|
|
|
21
21
|
cancelled: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export type DisplayMode = "overlay" | "inline";
|
|
25
|
-
|
|
26
24
|
export interface QuestionParams {
|
|
27
25
|
question: string;
|
|
28
26
|
context?: string;
|
|
@@ -30,8 +28,6 @@ export interface QuestionParams {
|
|
|
30
28
|
allowMultiple?: boolean;
|
|
31
29
|
allowFreeform?: boolean;
|
|
32
30
|
allowComment?: boolean;
|
|
33
|
-
displayMode?: DisplayMode;
|
|
34
|
-
overlayToggleKey?: string | null;
|
|
35
31
|
commentToggleKey?: string | null;
|
|
36
32
|
timeout?: number;
|
|
37
33
|
}
|
|
@@ -45,7 +41,6 @@ export type ResolvedShortcut =
|
|
|
45
41
|
| { disabled: true; spec: null; matches: (data: string) => false };
|
|
46
42
|
|
|
47
43
|
export interface ResolvedShortcuts {
|
|
48
|
-
overlayToggle: ResolvedShortcut;
|
|
49
44
|
commentToggle: ResolvedShortcut;
|
|
50
45
|
}
|
|
51
46
|
|
|
@@ -96,7 +91,6 @@ export interface ResolvedQuestionParams {
|
|
|
96
91
|
allowMultiple: boolean;
|
|
97
92
|
allowFreeform: boolean;
|
|
98
93
|
allowComment: boolean;
|
|
99
|
-
displayMode: DisplayMode;
|
|
100
94
|
shortcuts: ResolvedShortcuts;
|
|
101
95
|
timeout: number | undefined;
|
|
102
96
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// UIProtocol port — abstracts TUI vs dialog/RPC transport.
|
|
2
2
|
|
|
3
3
|
import type { KeybindingsManager } from "@selesai/code";
|
|
4
|
-
import type {
|
|
4
|
+
import type { Theme, TUI } from "@earendil-works/pi-tui";
|
|
5
5
|
|
|
6
6
|
import type { QuestionResponse, ResolvedQuestionParams } from "./types.ts";
|
|
7
7
|
|
|
@@ -18,27 +18,12 @@ export type CustomFactory = (
|
|
|
18
18
|
done: (result: QuestionResponse | null) => void,
|
|
19
19
|
) => CustomFactoryResult;
|
|
20
20
|
|
|
21
|
-
export interface CustomUIOptions {
|
|
22
|
-
overlay?: boolean;
|
|
23
|
-
overlayOptions?: {
|
|
24
|
-
anchor: string;
|
|
25
|
-
width: string;
|
|
26
|
-
minWidth: number;
|
|
27
|
-
maxHeight: string;
|
|
28
|
-
margin: number;
|
|
29
|
-
};
|
|
30
|
-
onHandle?: (handle: OverlayHandle) => void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
21
|
export interface UIProtocol {
|
|
34
22
|
readonly hasUI: boolean;
|
|
35
23
|
readonly theme: Theme;
|
|
36
|
-
custom(factory: CustomFactory
|
|
24
|
+
custom(factory: CustomFactory): Promise<QuestionResponse | null | undefined>;
|
|
37
25
|
select(prompt: string, options: string[], opts?: { timeout?: number }): Promise<string | undefined>;
|
|
38
26
|
input(prompt: string, placeholder?: string, opts?: { timeout?: number }): Promise<string | undefined>;
|
|
39
|
-
onTerminalInput?(handler: (data: string) => { consume: boolean } | void): () => void;
|
|
40
|
-
setStatus(key: string, text: string): void;
|
|
41
|
-
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
42
27
|
}
|
|
43
28
|
|
|
44
29
|
export interface FallbackProtocol {
|