@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.
Files changed (80) hide show
  1. package/dist/core/agent-session-auto-handoff.test.d.ts +2 -0
  2. package/dist/core/agent-session-auto-handoff.test.d.ts.map +1 -0
  3. package/dist/core/agent-session-auto-handoff.test.js +161 -0
  4. package/dist/core/agent-session-auto-handoff.test.js.map +1 -0
  5. package/dist/core/agent-session.d.ts +4 -0
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +43 -0
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/settings-manager-auto-handoff.test.d.ts +2 -0
  10. package/dist/core/settings-manager-auto-handoff.test.d.ts.map +1 -0
  11. package/dist/core/settings-manager-auto-handoff.test.js +29 -0
  12. package/dist/core/settings-manager-auto-handoff.test.js.map +1 -0
  13. package/dist/core/settings-manager.d.ts +9 -0
  14. package/dist/core/settings-manager.d.ts.map +1 -1
  15. package/dist/core/settings-manager.js +22 -0
  16. package/dist/core/settings-manager.js.map +1 -1
  17. package/dist/defaults/models.json +2 -3
  18. package/dist/extensions/caveman/index.js +16 -1
  19. package/dist/extensions/caveman/test/extension.test.js +7 -4
  20. package/dist/extensions/caveman/test/helpers.test.js +12 -1
  21. package/dist/extensions/context-compaction-reminder.test.ts +82 -0
  22. package/dist/extensions/context-compaction-reminder.ts +28 -0
  23. package/dist/extensions/handoff-new.test.ts +2 -13
  24. package/dist/extensions/handoff-new.ts +16 -25
  25. package/dist/extensions/package.json +1 -0
  26. package/dist/extensions/pi-powerline-footer/index.ts +43 -2
  27. package/dist/extensions/pi-powerline-footer/session-usage.ts +44 -0
  28. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +47 -0
  29. package/dist/extensions/pi-subagents/README.md +1 -1
  30. package/dist/extensions/pi-subagents/agents/architect.md +9 -29
  31. package/dist/extensions/pi-subagents/agents/builder.md +17 -106
  32. package/dist/extensions/pi-subagents/agents/commentator.md +20 -117
  33. package/dist/extensions/pi-subagents/agents/explorer.md +14 -35
  34. package/dist/extensions/pi-subagents/agents/recapper.md +20 -12
  35. package/dist/extensions/pi-subagents/agents/researcher.md +11 -34
  36. package/dist/extensions/pi-subagents/src/agents/agents.ts +46 -0
  37. package/dist/extensions/pi-subagents/src/extension/index.ts +2 -1
  38. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +2 -0
  39. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +24 -0
  40. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +25 -1
  41. package/dist/extensions/question/constants.ts +0 -7
  42. package/dist/extensions/question/index.ts +11 -108
  43. package/dist/extensions/question/schemas.ts +1 -26
  44. package/dist/extensions/question/shortcuts.ts +5 -14
  45. package/dist/extensions/question/tests/question-list.test.ts +8 -0
  46. package/dist/extensions/question/tests/ui-protocol.test.ts +1 -7
  47. package/dist/extensions/question/tui-adapter.ts +3 -20
  48. package/dist/extensions/question/types.ts +0 -6
  49. package/dist/extensions/question/ui-protocol.ts +2 -17
  50. package/dist/extensions/workflow/adapter.ts +395 -275
  51. package/dist/extensions/workflow/extension.ts +2 -1
  52. package/dist/extensions/workflow/modes/prototype.ts +17 -19
  53. package/dist/extensions/workflow/modes/quick.ts +17 -19
  54. package/dist/extensions/workflow/modes/task.ts +82 -0
  55. package/dist/extensions/workflow/run-state.ts +124 -0
  56. package/dist/extensions/workflow/state-machine.ts +7 -8
  57. package/dist/extensions/workflow/task-validators.ts +69 -0
  58. package/dist/index.d.ts +1 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  62. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  63. package/dist/modes/interactive/components/settings-selector.js +20 -0
  64. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  65. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  66. package/dist/modes/interactive/interactive-mode.js +8 -0
  67. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  68. package/dist/modes/rpc/rpc-client.d.ts +8 -0
  69. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  70. package/dist/modes/rpc/rpc-client.js +12 -0
  71. package/dist/modes/rpc/rpc-client.js.map +1 -1
  72. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  73. package/dist/modes/rpc/rpc-mode.js +14 -0
  74. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  75. package/dist/modes/rpc/rpc-types.d.ts +20 -0
  76. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  77. package/dist/modes/rpc/rpc-types.js.map +1 -1
  78. package/dist/skills/workflow-creation/SKILL.md +72 -0
  79. package/docs/workflows.md +46 -7
  80. 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, OVERLAY_MAX_HEIGHT_RATIO, QUESTION_STATUS_KEY, QUESTION_VERSION } from "./constants.ts";
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 { DisplayMode, QuestionDetails, QuestionMode, QuestionOption, QuestionResponse, RawOption, ResolvedShortcuts } from "./types.ts";
30
- import { buildCustomUIOptions, createTUIProtocol } from "./tui-adapter.ts";
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
- protocol.setStatus(
477
- QUESTION_STATUS_KEY,
478
- protocol.theme.fg("accent", `waiting: ${oneLine((normalizedContext ? `${normalizedContext}: ` : "") + rawQuestion, 48)}`),
479
- );
480
-
481
- // No options: pure freeform via dialog input.
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 overlayToggle = shortcuts.overlayToggle;
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, displayMode: effectiveDisplayMode, shortcuts, timeout },
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, type TUnsafe } from "typebox";
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 overlay-toggle and comment-toggle keys.
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
- overlayToggleKey: string | null | undefined,
45
- overlayEnv: string | undefined,
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
- displayMode: "overlay",
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 { OVERLAY_MIN_WIDTH, OVERLAY_OVERLAY_MAX_HEIGHT, OVERLAY_WIDTH } from "./constants.ts";
7
- import type { DisplayMode, QuestionResponse } from "./types.ts";
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, options) => ctx.ui.custom(factory as any, options as any) as Promise<QuestionResponse | null | undefined>,
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 { OverlayHandle, Theme, TUI } from "@earendil-works/pi-tui";
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, options?: CustomUIOptions): Promise<QuestionResponse | null | undefined>;
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 {