@gajae-code/coding-agent 0.12.6 → 0.12.8

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 (150) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/types/async/job-manager.d.ts +36 -1
  3. package/dist/types/commands/notify.d.ts +4 -0
  4. package/dist/types/config/file-lock.d.ts +3 -0
  5. package/dist/types/config/model-discovery-manager.d.ts +6 -1
  6. package/dist/types/config/model-registry.d.ts +2 -0
  7. package/dist/types/config/resolve-config-value.d.ts +2 -1
  8. package/dist/types/config/settings-schema.d.ts +17 -1
  9. package/dist/types/eval/backend.d.ts +2 -2
  10. package/dist/types/exec/bash-executor.d.ts +8 -10
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
  12. package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/components/hook-selector.d.ts +20 -0
  15. package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
  16. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  17. package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
  18. package/dist/types/sdk/broker/broker.d.ts +5 -1
  19. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
  20. package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
  21. package/dist/types/sdk/bus/config.d.ts +75 -113
  22. package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
  23. package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
  24. package/dist/types/sdk/bus/index.d.ts +1 -1
  25. package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
  26. package/dist/types/sdk/bus/notification-service.d.ts +31 -4
  27. package/dist/types/sdk/bus/session-control.d.ts +8 -3
  28. package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
  29. package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
  30. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  31. package/dist/types/sdk/client/discovery.d.ts +2 -1
  32. package/dist/types/sdk/host/host.d.ts +2 -23
  33. package/dist/types/sdk/host/query/handlers.d.ts +2 -0
  34. package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
  35. package/dist/types/sdk/index.d.ts +1 -0
  36. package/dist/types/sdk/providers.d.ts +22 -0
  37. package/dist/types/sdk/session-directory.d.ts +1 -1
  38. package/dist/types/session/artifacts.d.ts +5 -8
  39. package/dist/types/session/client-bridge.d.ts +0 -14
  40. package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
  41. package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
  42. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
  43. package/dist/types/session/internal/session-open-errors.d.ts +5 -0
  44. package/dist/types/session/messages.d.ts +1 -4
  45. package/dist/types/session/session-manager.d.ts +7 -6
  46. package/dist/types/session/session-storage.d.ts +2 -0
  47. package/dist/types/session/streaming-output.d.ts +0 -32
  48. package/dist/types/task/receipt.d.ts +1 -2
  49. package/dist/types/task/types.d.ts +43 -178
  50. package/dist/types/tools/bash.d.ts +1 -1
  51. package/dist/types/tools/fetch.d.ts +0 -3
  52. package/dist/types/tools/output-meta.d.ts +5 -37
  53. package/dist/types/tools/subagent.d.ts +15 -0
  54. package/dist/types/tools/todo-write.d.ts +3 -0
  55. package/dist/types/tools/tool-result.d.ts +0 -1
  56. package/package.json +10 -8
  57. package/scripts/build-sdk-package-smoke.ts +51 -0
  58. package/scripts/capture-notifications-settings-showcase.ts +1 -1
  59. package/src/async/job-manager.ts +430 -46
  60. package/src/cli/notify-cli.ts +204 -94
  61. package/src/commands/notify.ts +19 -8
  62. package/src/config/file-lock.ts +6 -0
  63. package/src/config/model-discovery-manager.ts +50 -8
  64. package/src/config/model-registry.ts +862 -75
  65. package/src/config/resolve-config-value.ts +10 -8
  66. package/src/config/settings-schema.ts +25 -15
  67. package/src/config/settings.ts +15 -15
  68. package/src/cursor.ts +13 -20
  69. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
  70. package/src/eval/backend.ts +2 -2
  71. package/src/exec/bash-executor.ts +151 -425
  72. package/src/gjc-runtime/session-state-sidecar.ts +12 -2
  73. package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
  74. package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
  75. package/src/gjc-runtime/workflow-manifest.ts +0 -12
  76. package/src/internal-urls/agent-protocol.ts +38 -98
  77. package/src/internal-urls/artifact-protocol.ts +17 -28
  78. package/src/internal-urls/docs-index.generated.ts +7 -7
  79. package/src/internal-urls/registry-helpers.ts +0 -62
  80. package/src/main.ts +43 -7
  81. package/src/modes/DESIGN.md +11 -11
  82. package/src/modes/acp/acp-agent.ts +0 -24
  83. package/src/modes/acp/acp-client-bridge.ts +11 -154
  84. package/src/modes/components/hook-selector.ts +155 -44
  85. package/src/modes/components/model-selector.ts +242 -49
  86. package/src/modes/components/notifications-settings-editor.ts +631 -85
  87. package/src/modes/controllers/extension-ui-controller.ts +114 -15
  88. package/src/modes/controllers/selector-controller.ts +464 -50
  89. package/src/prompts/tools/bash.md +2 -2
  90. package/src/sdk/acp/adapter.ts +9 -200
  91. package/src/sdk/broker/broker.ts +54 -10
  92. package/src/sdk/broker/lifecycle-ledger.ts +34 -1
  93. package/src/sdk/broker/lifecycle.ts +47 -50
  94. package/src/sdk/bus/chat-daemon-cli.ts +12 -5
  95. package/src/sdk/bus/chat-daemon-control.ts +35 -8
  96. package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
  97. package/src/sdk/bus/config.ts +534 -286
  98. package/src/sdk/bus/discord-live-provider.ts +57 -2
  99. package/src/sdk/bus/discord-provider.ts +21 -0
  100. package/src/sdk/bus/index.ts +105 -359
  101. package/src/sdk/bus/notification-orchestration.ts +371 -68
  102. package/src/sdk/bus/notification-service.ts +431 -130
  103. package/src/sdk/bus/session-control.ts +119 -44
  104. package/src/sdk/bus/slack-live-provider.ts +78 -2
  105. package/src/sdk/bus/slack-provider.ts +24 -0
  106. package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
  107. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  108. package/src/sdk/bus/telegram-daemon-control.ts +3 -3
  109. package/src/sdk/bus/telegram-daemon.ts +23 -15
  110. package/src/sdk/client/discovery.ts +10 -4
  111. package/src/sdk/host/host.ts +9 -187
  112. package/src/sdk/host/query/handlers.ts +20 -1
  113. package/src/sdk/host/reverse-leases.ts +42 -249
  114. package/src/sdk/index.ts +1 -0
  115. package/src/sdk/protocol/operation-inventory.generated.json +18 -0
  116. package/src/sdk/protocol/operation-registry.ts +5 -2
  117. package/src/sdk/providers.ts +51 -0
  118. package/src/sdk/session-directory.ts +1 -0
  119. package/src/sdk/session.ts +10 -7
  120. package/src/session/agent-session.ts +59 -202
  121. package/src/session/artifacts.ts +74 -211
  122. package/src/session/client-bridge.ts +0 -15
  123. package/src/session/internal/managed-session-scope.ts +981 -450
  124. package/src/session/internal/managed-session-storage.ts +362 -124
  125. package/src/session/internal/native-publish-outcome.ts +3 -1
  126. package/src/session/internal/session-open-errors.ts +11 -0
  127. package/src/session/messages.ts +7 -32
  128. package/src/session/session-manager.ts +48 -15
  129. package/src/session/session-storage.ts +72 -5
  130. package/src/session/streaming-output.ts +40 -198
  131. package/src/slash-commands/builtin-registry.ts +82 -4
  132. package/src/task/index.ts +237 -65
  133. package/src/task/receipt.ts +2 -4
  134. package/src/task/render.ts +0 -30
  135. package/src/task/types.ts +10 -34
  136. package/src/tools/ask.ts +1 -1
  137. package/src/tools/bash.ts +239 -818
  138. package/src/tools/eval.ts +4 -99
  139. package/src/tools/fetch.ts +15 -77
  140. package/src/tools/find.ts +3 -7
  141. package/src/tools/output-meta.ts +114 -696
  142. package/src/tools/search.ts +4 -9
  143. package/src/tools/subagent.ts +97 -75
  144. package/src/tools/telegram-send.ts +4 -2
  145. package/src/tools/todo-write.ts +47 -0
  146. package/src/tools/tool-result.ts +0 -1
  147. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
  148. package/dist/types/utils/artifact-id.d.ts +0 -2
  149. package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
  150. package/src/utils/artifact-id.ts +0 -6
@@ -47,6 +47,21 @@ export interface HookSelectorOptions {
47
47
  */
48
48
  wrapFocused?: boolean;
49
49
  scrollTitleRows?: number;
50
+ /**
51
+ * Maximum visible rows for the inline editor in bounded scroll-title selectors.
52
+ * The editor keeps the full draft and scrolls within this height.
53
+ */
54
+ inlineEditorMaxHeight?: number;
55
+ /**
56
+ * Maximum visible autocomplete rows for the inline editor in a bounded selector.
57
+ * The Editor retains its normal minimum and scroll indicator behavior.
58
+ */
59
+ inlineAutocompleteMaxVisible?: number;
60
+ /**
61
+ * Compact bounded-selector chrome while the inline editor is active.
62
+ * Used only when the terminal cannot fit the normal editor and dropdown rows.
63
+ */
64
+ compactInlineInput?: boolean;
50
65
  /**
51
66
  * Inline free-text entry for the option with this label (e.g. the ask
52
67
  * tool's "Other (type your own)"). Selecting it keeps the title and option
@@ -103,6 +118,7 @@ class ScrollableTitle extends Container {
103
118
  #maxRows: number;
104
119
  #scrollOffset = 0;
105
120
  #lastMaxScrollOffset = 0;
121
+ #lastRenderWidth?: number;
106
122
 
107
123
  constructor(title: string, maxRows: number) {
108
124
  super();
@@ -110,9 +126,15 @@ class ScrollableTitle extends Container {
110
126
  this.#markdown = new Markdown(title, 1, 0, getMarkdownTheme(), { color: t => theme.fg("accent", t) });
111
127
  }
112
128
 
113
- setText(text: string): void {
129
+ setText(text: string, resetScroll = true): void {
114
130
  this.#markdown.setText(text);
115
- this.#scrollOffset = 0;
131
+ if (resetScroll) this.#scrollOffset = 0;
132
+ this.invalidate();
133
+ }
134
+ setMaxRows(maxRows: number): void {
135
+ const wasAtBottom = this.#lastMaxScrollOffset > 0 && this.#scrollOffset >= this.#lastMaxScrollOffset;
136
+ this.#maxRows = Math.max(1, Math.floor(maxRows));
137
+ if (wasAtBottom) this.#scrollOffset = Number.MAX_SAFE_INTEGER;
116
138
  this.invalidate();
117
139
  }
118
140
 
@@ -125,7 +147,30 @@ class ScrollableTitle extends Container {
125
147
  }
126
148
 
127
149
  render(width: number): string[] {
128
- const lines = this.#markdown.render(width);
150
+ const wasAtBottom = this.#lastMaxScrollOffset > 0 && this.#scrollOffset >= this.#lastMaxScrollOffset;
151
+ let lines: string[];
152
+ if (this.#lastRenderWidth !== undefined && this.#lastRenderWidth !== width) {
153
+ const previous = this.#markdown.renderWithViewportAnchorSource(this.#lastRenderWidth, { id: "title" });
154
+ const previousOffset = Math.min(this.#scrollOffset, previous.anchors.length - 1);
155
+ const previousAnchor =
156
+ previous.anchors[previousOffset] ??
157
+ previous.anchors.slice(previousOffset + 1).find(anchor => anchor !== null) ??
158
+ previous.anchors.slice(0, previousOffset).findLast(anchor => anchor !== null);
159
+ const reflowed = this.#markdown.renderWithViewportAnchorSource(width, { id: "title" });
160
+ lines = reflowed.lines;
161
+ if (previousAnchor) {
162
+ const anchoredLine = reflowed.anchors.findIndex(
163
+ anchor =>
164
+ anchor !== null &&
165
+ anchor.graphemeStart <= previousAnchor.graphemeStart &&
166
+ previousAnchor.graphemeStart < anchor.graphemeEnd,
167
+ );
168
+ if (anchoredLine >= 0) this.#scrollOffset = anchoredLine;
169
+ }
170
+ } else {
171
+ lines = this.#markdown.render(width);
172
+ }
173
+ this.#lastRenderWidth = width;
129
174
  if (lines.length <= this.#maxRows) {
130
175
  this.#lastMaxScrollOffset = 0;
131
176
  this.#scrollOffset = 0;
@@ -137,22 +182,17 @@ class ScrollableTitle extends Container {
137
182
  this.#lastMaxScrollOffset = maxScrollOffset;
138
183
  this.#scrollOffset = Math.max(0, Math.min(this.#scrollOffset, maxScrollOffset));
139
184
 
140
- const visibleLines = lines.slice(this.#scrollOffset, this.#scrollOffset + this.#maxRows);
141
- const indicator =
142
- this.#scrollOffset === 0
143
- ? theme.fg("dim", " PgDn↓")
144
- : this.#scrollOffset >= maxScrollOffset
145
- ? theme.fg("dim", " PgUp↑")
146
- : theme.fg("dim", " PgUp/PgDn↕");
147
- const lastIndex = visibleLines.length - 1;
148
- const availableWidth = Math.max(1, width - visibleWidth(indicator));
149
- const fittedLine = truncateToWidth(visibleLines[lastIndex] ?? "", availableWidth);
150
- visibleLines[lastIndex] = `${fittedLine}${indicator}`;
151
- return visibleLines;
185
+ return lines.slice(this.#scrollOffset, this.#scrollOffset + this.#maxRows);
152
186
  }
153
187
 
188
+ if (wasAtBottom) {
189
+ // The previous render may have reached the bottom after converging
190
+ // away the bottom indicator. Re-anchor before recomputing indicators
191
+ // so a timer repaint cannot resurrect a stale `▼ more` row.
192
+ this.#scrollOffset = Math.max(0, lines.length - (this.#maxRows - 1));
193
+ }
154
194
  let showTopIndicator = this.#scrollOffset > 0;
155
- let showBottomIndicator = true;
195
+ let showBottomIndicator = !wasAtBottom;
156
196
  let contentRows = 1;
157
197
  let maxScrollOffset = 0;
158
198
 
@@ -234,12 +274,14 @@ class FocusAwareList extends Container {
234
274
  );
235
275
  const focusedWrappedSegments = wrapTextWithAnsi(focusedLabel, availableLabelWidth);
236
276
 
237
- // Reserve one row for the option position marker only when the focused
238
- // block itself must be compacted. Moderate focused labels keep the legacy
239
- // wrap-focused behavior and spend the full viewport on label context.
277
+ // Reserve one row when a non-full focused block and its sibling window
278
+ // cannot both fit. A full focused block keeps its existing priority.
240
279
  const totalOptions = this.#options.length;
241
280
  const mustCompactFocused = focusedWrappedSegments.length > this.#maxVisibleRows;
242
- const positionMarkerSlot = mustCompactFocused && totalOptions > 1 ? 1 : 0;
281
+ const mustMarkClippedSiblings =
282
+ focusedWrappedSegments.length < this.#maxVisibleRows &&
283
+ focusedWrappedSegments.length + totalOptions - 1 > this.#maxVisibleRows;
284
+ const positionMarkerSlot = (mustCompactFocused || mustMarkClippedSiblings) && totalOptions > 1 ? 1 : 0;
243
285
  const focusedBudget = Math.max(1, this.#maxVisibleRows - positionMarkerSlot);
244
286
  const focusedSegments = this.#capFocusedSegments(focusedWrappedSegments, focusedBudget, availableLabelWidth);
245
287
  const focusedRows = Math.max(1, focusedSegments.length);
@@ -296,7 +338,7 @@ class FocusAwareList extends Container {
296
338
  if (rows.length <= budget) return rows;
297
339
 
298
340
  if (budget === 1) {
299
- return [truncateToWidth(`… ${rows.length - 1} wrapped rows omitted …`, availableLabelWidth)];
341
+ return [truncateToWidth(rows[0] ?? "", availableLabelWidth)];
300
342
  }
301
343
 
302
344
  if (budget === 2) {
@@ -347,6 +389,7 @@ export class HookSelectorComponent extends Container {
347
389
  #wrapFocused: boolean;
348
390
  #outline: boolean;
349
391
  #scrollTitleRows: number | undefined;
392
+ #inlineEditorMaxHeight: number | undefined;
350
393
  #customInput: { optionLabel: string; onSubmit: (text: string) => void } | undefined;
351
394
  #clarificationInput: { optionLabel: string; onSubmit: (text: string) => void; allowEmpty?: boolean } | undefined;
352
395
  #activeInput: { onSubmit: (text: string) => void; allowEmpty?: boolean } | undefined;
@@ -357,6 +400,10 @@ export class HookSelectorComponent extends Container {
357
400
  #tui: TUI | undefined;
358
401
  #autocompleteProvider: AutocompleteProvider | undefined;
359
402
  #acceleratorMap: Readonly<Record<string, string>> | undefined;
403
+ #inlineAutocompleteMaxVisible: number | undefined;
404
+ #compactInlineInput: boolean;
405
+ #titleSpacer: Spacer;
406
+ #inputSpacer: Spacer;
360
407
  constructor(
361
408
  title: string,
362
409
  options: string[],
@@ -368,7 +415,6 @@ export class HookSelectorComponent extends Container {
368
415
 
369
416
  this.#options = options;
370
417
  this.#selectedIndex = Math.min(opts?.initialIndex ?? 0, options.length - 1);
371
- this.#maxVisible = Math.max(3, opts?.maxVisible ?? 12);
372
418
  this.#onSelectCallback = onSelect;
373
419
  this.#onCancelCallback = onCancel;
374
420
  this.#baseTitle = title;
@@ -382,13 +428,21 @@ export class HookSelectorComponent extends Container {
382
428
  this.#tui = opts?.tui;
383
429
  this.#autocompleteProvider = opts?.autocompleteProvider;
384
430
  this.#acceleratorMap = opts?.acceleratorMap;
431
+ this.#inlineAutocompleteMaxVisible =
432
+ opts?.inlineAutocompleteMaxVisible === undefined
433
+ ? undefined
434
+ : Math.max(3, Math.floor(opts.inlineAutocompleteMaxVisible));
435
+ this.#compactInlineInput = opts?.compactInlineInput === true;
385
436
 
386
437
  this.addChild(new DynamicBorder());
387
438
  this.addChild(new Spacer(1));
388
439
 
389
440
  const scrollTitleRows =
390
441
  opts?.scrollTitleRows === undefined ? undefined : Math.max(1, Math.floor(opts.scrollTitleRows));
442
+ this.#maxVisible = Math.max(scrollTitleRows === undefined ? 3 : 1, opts?.maxVisible ?? 12);
391
443
  this.#scrollTitleRows = scrollTitleRows;
444
+ this.#inlineEditorMaxHeight =
445
+ opts?.inlineEditorMaxHeight === undefined ? undefined : Math.max(1, Math.floor(opts.inlineEditorMaxHeight));
392
446
  if (scrollTitleRows === undefined) {
393
447
  this.#titleComponent = new Markdown(title, 1, 0, getMarkdownTheme(), { color: t => theme.fg("accent", t) });
394
448
  } else {
@@ -396,13 +450,20 @@ export class HookSelectorComponent extends Container {
396
450
  this.#titleComponent = this.#scrollableTitle;
397
451
  }
398
452
  this.addChild(this.#titleComponent);
399
- this.addChild(new Spacer(1));
453
+ this.#titleSpacer = new Spacer(1);
454
+ this.addChild(this.#titleSpacer);
400
455
 
401
456
  if (opts?.timeout && opts.timeout > 0 && opts.tui) {
402
457
  this.#countdown = new CountdownTimer(
403
458
  opts.timeout,
404
459
  opts.tui,
405
- s => this.#titleComponent.setText(`${this.#baseTitle} (${s}s)`),
460
+ s => {
461
+ if (this.#scrollableTitle) {
462
+ this.#scrollableTitle.setText(`${this.#baseTitle} (${s}s)`, false);
463
+ } else {
464
+ this.#titleComponent.setText(`${this.#baseTitle} (${s}s)`);
465
+ }
466
+ },
406
467
  () => {
407
468
  opts?.onTimeout?.();
408
469
  // Auto-select current option on timeout (typically the first/recommended option)
@@ -431,7 +492,8 @@ export class HookSelectorComponent extends Container {
431
492
  }
432
493
  this.#inputArea = new Container();
433
494
  this.addChild(this.#inputArea);
434
- this.addChild(new Spacer(1));
495
+ this.#inputSpacer = new Spacer(1);
496
+ this.addChild(this.#inputSpacer);
435
497
  this.#baseHelpText = opts?.helpText ?? "up/down navigate enter select esc cancel";
436
498
  this.#helpTextComponent = new Text(theme.fg("dim", this.#baseHelpText), 1, 0);
437
499
  this.addChild(this.#helpTextComponent);
@@ -444,6 +506,38 @@ export class HookSelectorComponent extends Container {
444
506
  hasActiveInlineInput(): boolean {
445
507
  return this.#inlineEditor !== undefined;
446
508
  }
509
+ /**
510
+ * Update the bounded selector's title/list row budgets after a terminal resize.
511
+ * The existing title scroll offset, option focus, and inline editor remain intact.
512
+ */
513
+ setLayoutBudget(
514
+ maxVisible: number,
515
+ scrollTitleRows: number,
516
+ inlineEditorMaxHeight = this.#inlineEditorMaxHeight,
517
+ inlineAutocompleteMaxVisible = this.#inlineAutocompleteMaxVisible,
518
+ compactInlineInput = this.#compactInlineInput,
519
+ ): void {
520
+ if (!this.#scrollableTitle) return;
521
+ const titleRows = Math.max(1, Math.floor(scrollTitleRows));
522
+ this.#maxVisible = Math.max(1, Math.floor(maxVisible));
523
+ this.#scrollTitleRows = titleRows;
524
+ this.#inlineEditorMaxHeight =
525
+ inlineEditorMaxHeight === undefined ? undefined : Math.max(1, Math.floor(inlineEditorMaxHeight));
526
+ this.#inlineAutocompleteMaxVisible =
527
+ inlineAutocompleteMaxVisible === undefined ? undefined : Math.max(3, Math.floor(inlineAutocompleteMaxVisible));
528
+ this.#compactInlineInput = compactInlineInput;
529
+ if (this.#inlineEditor) {
530
+ this.#inlineEditor.setMaxHeight(this.#inlineEditorMaxHeight);
531
+ if (this.#inlineAutocompleteMaxVisible !== undefined) {
532
+ this.#inlineEditor.setAutocompleteMaxVisible(this.#inlineAutocompleteMaxVisible);
533
+ }
534
+ this.#inlineEditor.setAutocompleteProvider(this.#compactInlineInput ? undefined : this.#autocompleteProvider);
535
+ }
536
+ this.#setCompactInputSpacing(this.#inlineEditor !== undefined && this.#compactInlineInput);
537
+ this.#scrollableTitle.setMaxRows(titleRows);
538
+ this.#updateList();
539
+ this.invalidate();
540
+ }
447
541
 
448
542
  #updateList(): void {
449
543
  if (this.#wrapFocused && this.#focusAwareList) {
@@ -489,21 +583,24 @@ export class HookSelectorComponent extends Container {
489
583
  // Reset countdown on any interaction
490
584
  this.#countdown?.reset();
491
585
 
492
- if (this.#scrollTitleRows !== undefined && matchesKey(keyData, "pageUp")) {
493
- this.#scrollableTitle?.scrollBy(-this.#scrollTitleRows);
494
- return;
495
- }
496
- if (this.#scrollTitleRows !== undefined && matchesKey(keyData, "pageDown")) {
497
- this.#scrollableTitle?.scrollBy(this.#scrollTitleRows);
498
- return;
499
- }
500
- if (!this.#inlineEditor && this.#scrollTitleRows !== undefined && matchesKey(keyData, "ctrl+u")) {
501
- this.#scrollableTitle?.scrollBy(-this.#scrollTitleRows);
502
- return;
503
- }
504
- if (!this.#inlineEditor && this.#scrollTitleRows !== undefined && matchesKey(keyData, "ctrl+d")) {
505
- this.#scrollableTitle?.scrollBy(this.#scrollTitleRows);
506
- return;
586
+ if (this.#scrollTitleRows !== undefined && !this.#inlineEditor?.isAutocompleteOpen()) {
587
+ const titlePageRows = Math.max(1, this.#scrollTitleRows - 2);
588
+ if (matchesKey(keyData, "pageUp")) {
589
+ this.#scrollableTitle?.scrollBy(-titlePageRows);
590
+ return;
591
+ }
592
+ if (matchesKey(keyData, "pageDown")) {
593
+ this.#scrollableTitle?.scrollBy(titlePageRows);
594
+ return;
595
+ }
596
+ if (!this.#inlineEditor && matchesKey(keyData, "ctrl+u")) {
597
+ this.#scrollableTitle?.scrollBy(-titlePageRows);
598
+ return;
599
+ }
600
+ if (!this.#inlineEditor && matchesKey(keyData, "ctrl+d")) {
601
+ this.#scrollableTitle?.scrollBy(titlePageRows);
602
+ return;
603
+ }
507
604
  }
508
605
  if (this.#inlineEditor) {
509
606
  this.#handleInputModeKey(keyData, this.#inlineEditor);
@@ -573,6 +670,10 @@ export class HookSelectorComponent extends Container {
573
670
  editor.handleInput(keyData);
574
671
  }
575
672
 
673
+ #setCompactInputSpacing(compact: boolean): void {
674
+ this.#titleSpacer.setLines(compact ? 0 : 1);
675
+ this.#inputSpacer.setLines(compact ? 0 : 1);
676
+ }
576
677
  #enterInputMode(input: { onSubmit: (text: string) => void; allowEmpty?: boolean }): void {
577
678
  if (this.#inlineEditor) return;
578
679
  this.#activeInput = input;
@@ -581,28 +682,37 @@ export class HookSelectorComponent extends Container {
581
682
  if (this.#countdown) {
582
683
  this.#countdown.dispose();
583
684
  this.#countdown = undefined;
584
- this.#titleComponent.setText(this.#baseTitle);
685
+ if (this.#scrollableTitle) {
686
+ this.#scrollableTitle.setText(this.#baseTitle, false);
687
+ } else {
688
+ this.#titleComponent.setText(this.#baseTitle);
689
+ }
585
690
  }
586
691
  const editor = new Editor(getEditorTheme());
587
692
  editor.setBorderVisible(false);
588
693
  editor.setPromptGutter("> ");
589
694
  editor.disableSubmit = true;
695
+ editor.setMaxHeight(this.#inlineEditorMaxHeight);
696
+ if (this.#inlineAutocompleteMaxVisible !== undefined) {
697
+ editor.setAutocompleteMaxVisible(this.#inlineAutocompleteMaxVisible);
698
+ }
590
699
  // Mark the inline editor focused only when mirroring the app's hardware-cursor
591
700
  // mode, so it emits CURSOR_MARKER at the input caret for IME preedit anchoring
592
701
  // without changing legacy non-hardware-cursor layout.
593
702
  const useTerminalCursor = this.#tui?.getShowHardwareCursor() ?? false;
594
703
  editor.focused = useTerminalCursor;
595
704
  editor.setUseTerminalCursor(useTerminalCursor);
596
- if (this.#autocompleteProvider) {
705
+ if (this.#autocompleteProvider && !this.#compactInlineInput) {
597
706
  editor.setAutocompleteProvider(this.#autocompleteProvider);
598
707
  }
599
708
  this.#inlineEditor = editor;
600
- this.#inputArea.addChild(new Spacer(1));
709
+ this.#setCompactInputSpacing(this.#compactInlineInput);
710
+ if (!this.#compactInlineInput) this.#inputArea.addChild(new Spacer(1));
601
711
  this.#inputArea.addChild(editor);
602
712
  const helpText =
603
713
  this.#scrollTitleRows === undefined
604
714
  ? "enter submit esc back to options ctrl+g external editor"
605
- : "enter submit esc back to options PgUp/PgDn: question · Wheel: transcript";
715
+ : "enter submit esc back to options ctrl+g external editor PgUp/PgDn: question · Wheel: transcript";
606
716
  this.#helpTextComponent.setText(theme.fg("dim", helpText));
607
717
  this.invalidate();
608
718
  }
@@ -612,6 +722,7 @@ export class HookSelectorComponent extends Container {
612
722
  this.#inlineEditor = undefined;
613
723
  this.#activeInput = undefined;
614
724
  this.#inputArea.clear();
725
+ this.#setCompactInputSpacing(false);
615
726
  this.#helpTextComponent.setText(theme.fg("dim", this.#baseHelpText));
616
727
  this.invalidate();
617
728
  }