pi-ui-extend 0.1.8 → 0.1.11

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 (191) hide show
  1. package/README.md +57 -2
  2. package/bin/pix.mjs +4 -4
  3. package/dist/app/app.d.ts +4 -0
  4. package/dist/app/app.js +112 -45
  5. package/dist/app/{cli.d.ts → cli/cli.d.ts} +1 -1
  6. package/dist/app/{cli.js → cli/cli.js} +1 -1
  7. package/dist/app/{install.d.ts → cli/install.d.ts} +2 -0
  8. package/dist/app/{install.js → cli/install.js} +18 -3
  9. package/dist/app/{command-controller.d.ts → commands/command-controller.d.ts} +1 -1
  10. package/dist/app/{command-controller.js → commands/command-controller.js} +4 -0
  11. package/dist/app/{command-host.d.ts → commands/command-host.d.ts} +7 -3
  12. package/dist/app/{command-model-actions.d.ts → commands/command-model-actions.d.ts} +6 -1
  13. package/dist/app/{command-model-actions.js → commands/command-model-actions.js} +106 -2
  14. package/dist/app/{command-navigation-actions.d.ts → commands/command-navigation-actions.d.ts} +7 -2
  15. package/dist/app/{command-navigation-actions.js → commands/command-navigation-actions.js} +42 -19
  16. package/dist/app/{command-registry.d.ts → commands/command-registry.d.ts} +5 -1
  17. package/dist/app/{command-registry.js → commands/command-registry.js} +32 -0
  18. package/dist/app/{command-runtime.js → commands/command-runtime.js} +1 -1
  19. package/dist/app/{command-session-actions.d.ts → commands/command-session-actions.d.ts} +1 -0
  20. package/dist/app/{command-session-actions.js → commands/command-session-actions.js} +18 -8
  21. package/dist/app/{shell-controller.d.ts → commands/shell-controller.d.ts} +2 -1
  22. package/dist/app/{shell-controller.js → commands/shell-controller.js} +2 -2
  23. package/dist/app/{slash-commands.d.ts → commands/slash-commands.d.ts} +2 -2
  24. package/dist/app/{slash-commands.js → commands/slash-commands.js} +1 -1
  25. package/dist/app/constants.d.ts +1 -1
  26. package/dist/app/constants.js +1 -1
  27. package/dist/app/{extension-actions-controller.d.ts → extensions/extension-actions-controller.d.ts} +1 -1
  28. package/dist/app/{extension-actions-controller.js → extensions/extension-actions-controller.js} +1 -1
  29. package/dist/app/{extension-ui-controller.d.ts → extensions/extension-ui-controller.d.ts} +3 -3
  30. package/dist/app/{extension-ui-controller.js → extensions/extension-ui-controller.js} +3 -3
  31. package/dist/app/icons.js +1 -1
  32. package/dist/app/input/autocomplete-controller.d.ts +52 -0
  33. package/dist/app/input/autocomplete-controller.js +352 -0
  34. package/dist/app/{input-action-controller.d.ts → input/input-action-controller.d.ts} +8 -7
  35. package/dist/app/{input-action-controller.js → input/input-action-controller.js} +24 -3
  36. package/dist/app/{input-controller.d.ts → input/input-controller.d.ts} +4 -3
  37. package/dist/app/{input-controller.js → input/input-controller.js} +3 -1
  38. package/dist/app/{input-paste-handler.d.ts → input/input-paste-handler.d.ts} +2 -1
  39. package/dist/app/{input-paste-handler.js → input/input-paste-handler.js} +25 -21
  40. package/dist/app/{native-modifiers.js → input/native-modifiers.js} +2 -2
  41. package/dist/app/{prompt-enhancer-controller.d.ts → input/prompt-enhancer-controller.d.ts} +5 -5
  42. package/dist/app/{prompt-enhancer-controller.js → input/prompt-enhancer-controller.js} +3 -3
  43. package/dist/app/{voice-controller.d.ts → input/voice-controller.d.ts} +3 -1
  44. package/dist/app/{voice-controller.js → input/voice-controller.js} +29 -17
  45. package/dist/app/{model-ref.d.ts → model/model-ref.d.ts} +1 -1
  46. package/dist/app/{model-ref.js → model/model-ref.js} +1 -1
  47. package/dist/app/{model-usage-controller.js → model/model-usage-controller.js} +1 -1
  48. package/dist/app/{model-usage-status.d.ts → model/model-usage-status.d.ts} +10 -1
  49. package/dist/app/{model-usage-status.js → model/model-usage-status.js} +125 -35
  50. package/dist/app/{menu-items-controller.d.ts → popup/menu-items-controller.d.ts} +4 -4
  51. package/dist/app/{menu-items-controller.js → popup/menu-items-controller.js} +5 -5
  52. package/dist/app/{popup-action-controller.d.ts → popup/popup-action-controller.d.ts} +4 -4
  53. package/dist/app/{popup-action-controller.js → popup/popup-action-controller.js} +3 -3
  54. package/dist/app/{popup-menu-controller.d.ts → popup/popup-menu-controller.d.ts} +4 -4
  55. package/dist/app/{popup-menu-controller.js → popup/popup-menu-controller.js} +7 -7
  56. package/dist/app/process.d.ts +17 -0
  57. package/dist/app/process.js +68 -0
  58. package/dist/app/{conversation-entry-renderer.d.ts → rendering/conversation-entry-renderer.d.ts} +3 -3
  59. package/dist/app/{conversation-entry-renderer.js → rendering/conversation-entry-renderer.js} +20 -9
  60. package/dist/app/{conversation-shell-renderer.d.ts → rendering/conversation-shell-renderer.d.ts} +1 -1
  61. package/dist/app/{conversation-shell-renderer.js → rendering/conversation-shell-renderer.js} +1 -1
  62. package/dist/app/{conversation-tool-renderer.d.ts → rendering/conversation-tool-renderer.d.ts} +3 -3
  63. package/dist/app/{conversation-tool-renderer.js → rendering/conversation-tool-renderer.js} +10 -9
  64. package/dist/app/{conversation-viewport.d.ts → rendering/conversation-viewport.d.ts} +3 -3
  65. package/dist/app/{dcp-stats.js → rendering/dcp-stats.js} +1 -1
  66. package/dist/app/{editor-layout-renderer.d.ts → rendering/editor-layout-renderer.d.ts} +4 -3
  67. package/dist/app/{editor-layout-renderer.js → rendering/editor-layout-renderer.js} +13 -3
  68. package/dist/app/{editor-panels.d.ts → rendering/editor-panels.d.ts} +2 -2
  69. package/dist/app/{editor-panels.js → rendering/editor-panels.js} +4 -4
  70. package/dist/app/{message-content.d.ts → rendering/message-content.d.ts} +1 -1
  71. package/dist/app/{message-content.js → rendering/message-content.js} +66 -8
  72. package/dist/app/{render-controller.d.ts → rendering/render-controller.d.ts} +6 -6
  73. package/dist/app/{render-controller.js → rendering/render-controller.js} +11 -6
  74. package/dist/app/{render-text.d.ts → rendering/render-text.d.ts} +5 -2
  75. package/dist/app/{render-text.js → rendering/render-text.js} +53 -5
  76. package/dist/app/{status-line-renderer.d.ts → rendering/status-line-renderer.d.ts} +8 -4
  77. package/dist/app/{status-line-renderer.js → rendering/status-line-renderer.js} +73 -29
  78. package/dist/app/{tab-line-renderer.d.ts → rendering/tab-line-renderer.d.ts} +3 -3
  79. package/dist/app/{tab-line-renderer.js → rendering/tab-line-renderer.js} +2 -2
  80. package/dist/app/{toast-controller.d.ts → rendering/toast-controller.d.ts} +1 -1
  81. package/dist/app/{toast-controller.js → rendering/toast-controller.js} +2 -2
  82. package/dist/app/{toast-renderer.d.ts → rendering/toast-renderer.d.ts} +3 -3
  83. package/dist/app/{toast-renderer.js → rendering/toast-renderer.js} +3 -3
  84. package/dist/app/{tool-block-renderer.d.ts → rendering/tool-block-renderer.d.ts} +5 -5
  85. package/dist/app/{tool-block-renderer.js → rendering/tool-block-renderer.js} +15 -33
  86. package/dist/app/runtime.d.ts +6 -1
  87. package/dist/app/runtime.js +35 -2
  88. package/dist/app/{blink-controller.js → screen/blink-controller.js} +1 -1
  89. package/dist/app/{clipboard.d.ts → screen/clipboard.d.ts} +2 -2
  90. package/dist/app/{clipboard.js → screen/clipboard.js} +13 -18
  91. package/dist/app/{image-click-targets.d.ts → screen/image-click-targets.d.ts} +2 -2
  92. package/dist/app/{image-opener.d.ts → screen/image-opener.d.ts} +1 -1
  93. package/dist/app/{mouse-controller.d.ts → screen/mouse-controller.d.ts} +17 -10
  94. package/dist/app/{mouse-controller.js → screen/mouse-controller.js} +72 -29
  95. package/dist/app/{screen-selection.d.ts → screen/screen-selection.d.ts} +1 -1
  96. package/dist/app/{screen-styler.d.ts → screen/screen-styler.d.ts} +6 -3
  97. package/dist/app/{screen-styler.js → screen/screen-styler.js} +7 -6
  98. package/dist/app/{scroll-controller.d.ts → screen/scroll-controller.d.ts} +3 -3
  99. package/dist/app/{scroll-controller.js → screen/scroll-controller.js} +1 -1
  100. package/dist/app/{status-controller.d.ts → screen/status-controller.d.ts} +5 -2
  101. package/dist/app/{status-controller.js → screen/status-controller.js} +24 -9
  102. package/dist/app/{queued-message-controller.d.ts → session/queued-message-controller.d.ts} +9 -3
  103. package/dist/app/{queued-message-controller.js → session/queued-message-controller.js} +34 -23
  104. package/dist/app/{request-history.js → session/request-history.js} +2 -2
  105. package/dist/app/session/resume-session-loader.d.ts +15 -0
  106. package/dist/app/session/resume-session-loader.js +204 -0
  107. package/dist/app/{session-event-controller.d.ts → session/session-event-controller.d.ts} +8 -4
  108. package/dist/app/{session-event-controller.js → session/session-event-controller.js} +75 -8
  109. package/dist/app/{session-history.d.ts → session/session-history.d.ts} +1 -1
  110. package/dist/app/{session-history.js → session/session-history.js} +7 -6
  111. package/dist/app/{session-lifecycle-controller.d.ts → session/session-lifecycle-controller.d.ts} +7 -2
  112. package/dist/app/{session-lifecycle-controller.js → session/session-lifecycle-controller.js} +13 -5
  113. package/dist/app/{session-search.d.ts → session/session-search.d.ts} +1 -1
  114. package/dist/app/{session-search.js → session/session-search.js} +3 -3
  115. package/dist/app/{tabs-controller.d.ts → session/tabs-controller.d.ts} +11 -2
  116. package/dist/app/{tabs-controller.js → session/tabs-controller.js} +105 -9
  117. package/dist/app/{subagents-files.d.ts → subagents/subagents-files.d.ts} +1 -1
  118. package/dist/app/{subagents-files.js → subagents/subagents-files.js} +1 -1
  119. package/dist/app/{subagents-model.d.ts → subagents/subagents-model.d.ts} +1 -1
  120. package/dist/app/{subagents-model.js → subagents/subagents-model.js} +4 -4
  121. package/dist/app/{subagents-widget-controller.d.ts → subagents/subagents-widget-controller.d.ts} +1 -1
  122. package/dist/app/{subagents-widget-controller.js → subagents/subagents-widget-controller.js} +2 -2
  123. package/dist/app/{nerd-font-controller.js → terminal/nerd-font-controller.js} +16 -17
  124. package/dist/app/{terminal-bell-sound-controller.js → terminal/terminal-bell-sound-controller.js} +1 -1
  125. package/dist/app/{terminal-controller.d.ts → terminal/terminal-controller.d.ts} +1 -0
  126. package/dist/app/{terminal-controller.js → terminal/terminal-controller.js} +3 -2
  127. package/dist/app/{todo-model.d.ts → todo/todo-model.d.ts} +1 -1
  128. package/dist/app/{todo-model.js → todo/todo-model.js} +3 -3
  129. package/dist/app/{todo-widget-controller.d.ts → todo/todo-widget-controller.d.ts} +1 -1
  130. package/dist/app/{todo-widget-controller.js → todo/todo-widget-controller.js} +2 -2
  131. package/dist/app/types.d.ts +16 -2
  132. package/dist/app/{workspace-actions-controller.d.ts → workspace/workspace-actions-controller.d.ts} +2 -2
  133. package/dist/app/{workspace-actions-controller.js → workspace/workspace-actions-controller.js} +6 -6
  134. package/dist/app/{workspace-undo.d.ts → workspace/workspace-undo.d.ts} +1 -1
  135. package/dist/app/{workspace-undo.js → workspace/workspace-undo.js} +22 -20
  136. package/dist/config.d.ts +27 -0
  137. package/dist/config.js +174 -1
  138. package/dist/default-pix-config.js +38 -353
  139. package/dist/input-editor.d.ts +7 -1
  140. package/dist/input-editor.js +47 -6
  141. package/dist/main.js +2 -2
  142. package/dist/markdown-format.d.ts +1 -0
  143. package/dist/markdown-format.js +26 -1
  144. package/external/pi-tools-suite/README.md +78 -0
  145. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +4 -0
  146. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -1
  147. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -0
  148. package/external/pi-tools-suite/src/dcp/prompts.ts +5 -0
  149. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +314 -193
  150. package/external/pi-tools-suite/src/index.ts +1 -0
  151. package/external/pi-tools-suite/src/lib/lsp.ts +2 -1
  152. package/external/pi-tools-suite/src/lsp/_shared/output.ts +8 -7
  153. package/external/pi-tools-suite/src/lsp/manager.ts +4 -4
  154. package/external/pi-tools-suite/src/opencode-import/commands.ts +86 -0
  155. package/external/pi-tools-suite/src/opencode-import/importer.ts +208 -0
  156. package/external/pi-tools-suite/src/opencode-import/index.ts +25 -0
  157. package/external/pi-tools-suite/src/repo-discovery/index.ts +49 -2
  158. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +9 -1
  159. package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
  160. package/package.json +1 -1
  161. /package/dist/app/{startup-checks.d.ts → cli/startup-checks.d.ts} +0 -0
  162. /package/dist/app/{startup-checks.js → cli/startup-checks.js} +0 -0
  163. /package/dist/app/{startup-info.d.ts → cli/startup-info.d.ts} +0 -0
  164. /package/dist/app/{startup-info.js → cli/startup-info.js} +0 -0
  165. /package/dist/app/{update.d.ts → cli/update.d.ts} +0 -0
  166. /package/dist/app/{update.js → cli/update.js} +0 -0
  167. /package/dist/app/{command-host.js → commands/command-host.js} +0 -0
  168. /package/dist/app/{command-runtime.d.ts → commands/command-runtime.d.ts} +0 -0
  169. /package/dist/app/{shell-command.d.ts → commands/shell-command.d.ts} +0 -0
  170. /package/dist/app/{shell-command.js → commands/shell-command.js} +0 -0
  171. /package/dist/app/{extension-event-bus.d.ts → extensions/extension-event-bus.d.ts} +0 -0
  172. /package/dist/app/{extension-event-bus.js → extensions/extension-event-bus.js} +0 -0
  173. /package/dist/app/{native-modifiers.d.ts → input/native-modifiers.d.ts} +0 -0
  174. /package/dist/app/{terminal-edit-shortcuts.d.ts → input/terminal-edit-shortcuts.d.ts} +0 -0
  175. /package/dist/app/{terminal-edit-shortcuts.js → input/terminal-edit-shortcuts.js} +0 -0
  176. /package/dist/app/{model-usage-controller.d.ts → model/model-usage-controller.d.ts} +0 -0
  177. /package/dist/app/{conversation-viewport.js → rendering/conversation-viewport.js} +0 -0
  178. /package/dist/app/{dcp-stats.d.ts → rendering/dcp-stats.d.ts} +0 -0
  179. /package/dist/app/{blink-controller.d.ts → screen/blink-controller.d.ts} +0 -0
  180. /package/dist/app/{file-link-opener.d.ts → screen/file-link-opener.d.ts} +0 -0
  181. /package/dist/app/{file-link-opener.js → screen/file-link-opener.js} +0 -0
  182. /package/dist/app/{file-links.d.ts → screen/file-links.d.ts} +0 -0
  183. /package/dist/app/{file-links.js → screen/file-links.js} +0 -0
  184. /package/dist/app/{image-click-targets.js → screen/image-click-targets.js} +0 -0
  185. /package/dist/app/{image-opener.js → screen/image-opener.js} +0 -0
  186. /package/dist/app/{screen-selection.js → screen/screen-selection.js} +0 -0
  187. /package/dist/app/{request-history.d.ts → session/request-history.d.ts} +0 -0
  188. /package/dist/app/{nerd-font-controller.d.ts → terminal/nerd-font-controller.d.ts} +0 -0
  189. /package/dist/app/{terminal-bell-sound-controller.d.ts → terminal/terminal-bell-sound-controller.d.ts} +0 -0
  190. /package/dist/app/{terminal-output-buffer.d.ts → terminal/terminal-output-buffer.d.ts} +0 -0
  191. /package/dist/app/{terminal-output-buffer.js → terminal/terminal-output-buffer.js} +0 -0
@@ -1,13 +1,14 @@
1
1
  import { copyTextToClipboard } from "./clipboard.js";
2
- import { stringifyUnknown } from "./message-content.js";
3
- import { horizontalPaddingLayout } from "./render-text.js";
2
+ import { stringifyUnknown } from "../rendering/message-content.js";
3
+ import { horizontalPaddingLayout } from "../rendering/render-text.js";
4
4
  import { orderedSelection, samePoint } from "./screen-selection.js";
5
5
  import { openImageContent as openSystemImageContent } from "./image-opener.js";
6
- import { stringDisplayWidth } from "../terminal-width.js";
7
- import { formatDcpStatsToast } from "./dcp-stats.js";
6
+ import { stringDisplayWidth } from "../../terminal-width.js";
7
+ import { formatDcpStatsToast } from "../rendering/dcp-stats.js";
8
8
  import { detectFileLinks } from "./file-links.js";
9
9
  import { openFileLink as openDetectedFileLink } from "./file-link-opener.js";
10
10
  const CLICK_FLASH_MS = 100;
11
+ const LOST_MOUSE_RELEASE_SETTLE_MS = 180;
11
12
  export class AppMouseController {
12
13
  host;
13
14
  popupMenus;
@@ -23,6 +24,7 @@ export class AppMouseController {
23
24
  statusContextTarget;
24
25
  statusModelUsageTarget;
25
26
  statusUserJumpTarget;
27
+ statusDraftQueueTarget;
26
28
  statusThinkingExpandTarget;
27
29
  statusCompactToolsTarget;
28
30
  statusTerminalBellSoundTarget;
@@ -40,6 +42,7 @@ export class AppMouseController {
40
42
  autoScrollAccumulator = 0;
41
43
  autoScrollLastTick = 0;
42
44
  autoScrollCursorX = 1;
45
+ leftEdgeReleaseFallbackTimer;
43
46
  clickFlash;
44
47
  clickFlashTimer;
45
48
  clickFlashDirty = false;
@@ -73,6 +76,8 @@ export class AppMouseController {
73
76
  return;
74
77
  if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusModelUsageClick(event)))
75
78
  return;
79
+ if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusDraftQueueClick(event)))
80
+ return;
76
81
  if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusUserJumpClick(event)))
77
82
  return;
78
83
  if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusThinkingExpandClick(event)))
@@ -284,6 +289,7 @@ export class AppMouseController {
284
289
  this.statusThinkingTarget,
285
290
  this.statusContextTarget,
286
291
  this.statusModelUsageTarget,
292
+ this.statusDraftQueueTarget,
287
293
  this.statusUserJumpTarget,
288
294
  this.statusThinkingExpandTarget,
289
295
  this.statusCompactToolsTarget,
@@ -350,7 +356,7 @@ export class AppMouseController {
350
356
  const baseButton = event.button & 3;
351
357
  const draggingLeftButton = (event.button & 32) !== 0 && baseButton === 0;
352
358
  if (event.released) {
353
- if (!this.scrollBarDragActive && !onScrollBar)
359
+ if (!this.scrollBarDragActive)
354
360
  return false;
355
361
  this.scrollBarDragActive = false;
356
362
  return true;
@@ -380,7 +386,7 @@ export class AppMouseController {
380
386
  const baseButton = event.button & 3;
381
387
  const draggingLeftButton = (event.button & 32) !== 0 && baseButton === 0;
382
388
  if (event.released)
383
- return this.finishInputScrollBarDrag(event, onScrollBar);
389
+ return this.finishInputScrollBarDrag(event);
384
390
  if (event.button === 0 && onScrollBar) {
385
391
  this.inputScrollBarDragActive = true;
386
392
  this.scrollInputToTrackRow(editorRow, scrollBar.trackHeight, renderedInput.totalLineCount, renderedInput.visibleRowCount);
@@ -392,10 +398,10 @@ export class AppMouseController {
392
398
  this.scrollInputToTrackRow(trackRow, scrollBar.trackHeight, renderedInput.totalLineCount, renderedInput.visibleRowCount);
393
399
  return true;
394
400
  }
395
- finishInputScrollBarDrag(event, onScrollBar = false) {
401
+ finishInputScrollBarDrag(event) {
396
402
  if (!event.released)
397
403
  return false;
398
- if (!this.inputScrollBarDragActive && !onScrollBar)
404
+ if (!this.inputScrollBarDragActive)
399
405
  return false;
400
406
  this.inputScrollBarDragActive = false;
401
407
  return true;
@@ -507,6 +513,15 @@ export class AppMouseController {
507
513
  this.host.render();
508
514
  return true;
509
515
  }
516
+ handleStatusDraftQueueClick(event) {
517
+ const target = this.statusDraftQueueTarget;
518
+ if (!target)
519
+ return false;
520
+ if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
521
+ return false;
522
+ void this.host.queueInputFromStatus?.();
523
+ return true;
524
+ }
510
525
  handleStatusThinkingExpandClick(event) {
511
526
  const target = this.statusThinkingExpandTarget;
512
527
  if (!target)
@@ -634,42 +649,27 @@ export class AppMouseController {
634
649
  return true;
635
650
  }
636
651
  handleMouseSelection(event) {
637
- const point = { x: event.x, y: event.y };
638
652
  const baseButton = event.button & 3;
639
653
  const draggingLeftButton = (event.button & 32) !== 0 && baseButton === 0;
640
654
  if (event.released) {
641
655
  if (!this.mouseSelection)
642
656
  return false;
643
- this.stopAutoScroll();
657
+ this.cancelLeftEdgeReleaseFallback();
644
658
  this.updateSelectionCurrentFromMouse(event, { autoScroll: false });
645
- const selection = this.mouseSelection;
646
- this.mouseSelection = undefined;
647
- if (!selection.moved && samePoint(selection.anchor, point)) {
648
- this.host.render();
649
- return false;
650
- }
651
- const selectedText = this.getSelectedText(selection);
652
- this.host.render();
653
- if (selectedText.trim().length === 0)
654
- return true;
655
- try {
656
- this.copyTextToClipboard(selectedText);
657
- this.host.showToast("Copied to clipboard", "success");
658
- }
659
- catch (error) {
660
- this.host.showToast(`Copy failed: ${stringifyUnknown(error)}`, "error");
661
- }
662
- return true;
659
+ return this.finishMouseSelection();
663
660
  }
664
661
  if (draggingLeftButton) {
665
662
  if (!this.mouseSelection)
666
663
  return false;
667
664
  this.updateSelectionCurrentFromMouse(event);
665
+ this.updateLeftEdgeReleaseFallback(event);
668
666
  this.host.render();
669
667
  return true;
670
668
  }
671
669
  if (event.button === 0) {
670
+ this.cancelLeftEdgeReleaseFallback();
672
671
  this.stopAutoScroll();
672
+ const point = { x: event.x, y: event.y };
673
673
  const conversationPoint = this.conversationPointFromMouse(event, false);
674
674
  this.mouseSelection = conversationPoint
675
675
  ? {
@@ -685,6 +685,47 @@ export class AppMouseController {
685
685
  }
686
686
  return false;
687
687
  }
688
+ finishMouseSelection() {
689
+ const selection = this.mouseSelection;
690
+ if (!selection)
691
+ return false;
692
+ this.cancelLeftEdgeReleaseFallback();
693
+ this.stopAutoScroll();
694
+ this.mouseSelection = undefined;
695
+ if (!selection.moved && samePoint(selection.anchor, selection.current)) {
696
+ this.host.render();
697
+ return false;
698
+ }
699
+ const selectedText = this.getSelectedText(selection);
700
+ this.host.render();
701
+ if (selectedText.trim().length === 0)
702
+ return true;
703
+ try {
704
+ this.copyTextToClipboard(selectedText);
705
+ this.host.showToast("Copied to clipboard", "success");
706
+ }
707
+ catch (error) {
708
+ this.host.showToast(`Copy failed: ${stringifyUnknown(error)}`, "error");
709
+ }
710
+ return true;
711
+ }
712
+ updateLeftEdgeReleaseFallback(event) {
713
+ if (!this.mouseSelection?.moved || event.x > 1) {
714
+ this.cancelLeftEdgeReleaseFallback();
715
+ return;
716
+ }
717
+ this.cancelLeftEdgeReleaseFallback();
718
+ this.leftEdgeReleaseFallbackTimer = setTimeout(() => {
719
+ this.leftEdgeReleaseFallbackTimer = undefined;
720
+ this.finishMouseSelection();
721
+ }, LOST_MOUSE_RELEASE_SETTLE_MS);
722
+ }
723
+ cancelLeftEdgeReleaseFallback() {
724
+ if (!this.leftEdgeReleaseFallbackTimer)
725
+ return;
726
+ clearTimeout(this.leftEdgeReleaseFallbackTimer);
727
+ this.leftEdgeReleaseFallbackTimer = undefined;
728
+ }
688
729
  syncConversationSelectionForRender(startLine, bodyHeight, topReservedRows, width) {
689
730
  this.renderedConversationFrame = {
690
731
  bodyHeight,
@@ -741,7 +782,9 @@ export class AppMouseController {
741
782
  return this.getSelectedScreenText(selection.anchor, selection.current);
742
783
  }
743
784
  copyTextToClipboard(text) {
744
- (this.host.copyTextToClipboard ?? copyTextToClipboard)(text);
785
+ void Promise.resolve((this.host.copyTextToClipboard ?? copyTextToClipboard)(text)).catch((error) => {
786
+ this.host.showToast(error instanceof Error ? error.message : String(error), "error");
787
+ });
745
788
  }
746
789
  getSelectedScreenText(anchor, current) {
747
790
  const range = orderedSelection(anchor, current);
@@ -1,4 +1,4 @@
1
- import type { ScreenPoint } from "./types.js";
1
+ import type { ScreenPoint } from "../types.js";
2
2
  export declare function samePoint(left: ScreenPoint, right: ScreenPoint): boolean;
3
3
  export declare function orderedSelection(anchor: ScreenPoint, current: ScreenPoint): {
4
4
  start: ScreenPoint;
@@ -1,5 +1,5 @@
1
- import { type Theme } from "../theme.js";
2
- import type { MouseSelection, RenderedLine, StyledSegment } from "./types.js";
1
+ import { type Theme } from "../../theme.js";
2
+ import type { MouseSelection, RenderedLine, StyledSegment } from "../types.js";
3
3
  export type ScreenStylerHost = {
4
4
  readonly theme: Theme;
5
5
  readonly cwd?: string;
@@ -24,7 +24,10 @@ export declare class ScreenStyler {
24
24
  styleInputLine(row: number, text: string, tagSpans: readonly {
25
25
  start: number;
26
26
  end: number;
27
- }[] | undefined, width: number, tagColor: string, frameColor?: string): string;
27
+ }[] | undefined, suggestionSpans: readonly {
28
+ start: number;
29
+ end: number;
30
+ }[] | undefined, width: number, tagColor: string, suggestionColor: string, frameColor?: string): string;
28
31
  private styleAnsiLine;
29
32
  selectionRangeForRow(row: number, width: number): {
30
33
  startIndex: number;
@@ -1,7 +1,7 @@
1
- import { ANSI_RESET, colorize } from "../theme.js";
2
- import { renderMarkdownLine } from "../markdown-format.js";
3
- import { syntaxHighlightSegmentsForLine } from "../syntax-highlight.js";
4
- import { padOrTrimPlain } from "./render-text.js";
1
+ import { ANSI_RESET, colorize } from "../../theme.js";
2
+ import { renderMarkdownLine } from "../../markdown-format.js";
3
+ import { syntaxHighlightSegmentsForLine } from "../../syntax-highlight.js";
4
+ import { padOrTrimPlain } from "../rendering/render-text.js";
5
5
  import { orderedSelection } from "./screen-selection.js";
6
6
  export class ScreenStyler {
7
7
  host;
@@ -72,14 +72,14 @@ export class ScreenStyler {
72
72
  colorize(after, options),
73
73
  ].join("");
74
74
  }
75
- styleInputLine(row, text, tagSpans, width, tagColor, frameColor) {
75
+ styleInputLine(row, text, tagSpans, suggestionSpans, width, tagColor, suggestionColor, frameColor) {
76
76
  const colors = this.host.theme.colors;
77
77
  const baseOptions = { foreground: colors.inputForeground };
78
78
  if (this.selectionRangeForRow(row, width))
79
79
  return this.styleLine(row, text, width, baseOptions);
80
80
  const plain = padOrTrimPlain(text, width);
81
81
  const frameSpans = inputFrameSpans(plain, width, frameColor);
82
- if ((!tagSpans || tagSpans.length === 0) && frameSpans.length === 0) {
82
+ if ((!tagSpans || tagSpans.length === 0) && (!suggestionSpans || suggestionSpans.length === 0) && frameSpans.length === 0) {
83
83
  return hasAnsi(plain) ? this.styleAnsiLine(plain, baseOptions) : colorize(plain, baseOptions);
84
84
  }
85
85
  const chunks = [];
@@ -88,6 +88,7 @@ export class ScreenStyler {
88
88
  const spans = [
89
89
  ...frameSpans,
90
90
  ...(tagSpans ?? []).map((span) => ({ ...span, foreground: tagColor, bold: true })),
91
+ ...(suggestionSpans ?? []).map((span) => ({ ...span, foreground: suggestionColor })),
91
92
  ].sort((a, b) => a.start - b.start || a.end - b.end);
92
93
  for (const span of spans) {
93
94
  const start = Math.max(offset, Math.min(endOffset, span.start));
@@ -1,6 +1,6 @@
1
- import type { ConversationViewport } from "./conversation-viewport.js";
2
- import type { EditorLayoutRenderer } from "./editor-layout-renderer.js";
3
- import type { RenderedLine } from "./types.js";
1
+ import type { ConversationViewport } from "../rendering/conversation-viewport.js";
2
+ import type { EditorLayoutRenderer } from "../rendering/editor-layout-renderer.js";
3
+ import type { RenderedLine } from "../types.js";
4
4
  export type AppScrollMetrics = {
5
5
  bodyHeight: number;
6
6
  viewportColumns: number;
@@ -1,4 +1,4 @@
1
- import { sanitizeText } from "./render-text.js";
1
+ import { sanitizeText } from "../rendering/render-text.js";
2
2
  export class AppScrollController {
3
3
  host;
4
4
  scrollFromBottom = 0;
@@ -1,18 +1,20 @@
1
1
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
- import type { Theme } from "../theme.js";
2
+ import type { Theme } from "../../theme.js";
3
3
  import type { AppBlinkController } from "./blink-controller.js";
4
- import type { SessionActivity } from "./types.js";
4
+ import type { SessionActivity } from "../types.js";
5
5
  export type AppStatusControllerHost = {
6
6
  readonly cwd: string;
7
7
  readonly theme: Theme;
8
8
  readonly blinkController: AppBlinkController;
9
9
  runtimeSession(): AgentSession | undefined;
10
+ render(): void;
10
11
  };
11
12
  export declare class AppStatusController {
12
13
  private readonly host;
13
14
  private status;
14
15
  private statusFollowsSession;
15
16
  private gitBranchCache;
17
+ private gitBranchLookupInFlight;
16
18
  sessionActivity: SessionActivity;
17
19
  get statusDotBright(): boolean;
18
20
  constructor(host: AppStatusControllerHost);
@@ -31,5 +33,6 @@ export declare class AppStatusController {
31
33
  roundedContextUsagePercent(session: AgentSession): number | undefined;
32
34
  contextUsagePercentColor(percent: number): string;
33
35
  private currentGitBranchName;
36
+ private refreshGitBranchName;
34
37
  private startStatusBlink;
35
38
  }
@@ -1,12 +1,13 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import { basename } from "node:path";
3
- import { GIT_BRANCH_CACHE_MS } from "./constants.js";
2
+ import { GIT_BRANCH_CACHE_MS } from "../constants.js";
3
+ import { runProcess } from "../process.js";
4
4
  const STATUS_DOT_BLINK_KEY = "status-dot";
5
5
  export class AppStatusController {
6
6
  host;
7
7
  status = "starting";
8
8
  statusFollowsSession = false;
9
9
  gitBranchCache;
10
+ gitBranchLookupInFlight = false;
10
11
  sessionActivity = "idle";
11
12
  get statusDotBright() {
12
13
  return this.host.blinkController.visible(STATUS_DOT_BLINK_KEY, false);
@@ -87,13 +88,27 @@ export class AppStatusController {
87
88
  if (this.gitBranchCache && now - this.gitBranchCache.checkedAt < GIT_BRANCH_CACHE_MS) {
88
89
  return this.gitBranchCache.branch;
89
90
  }
90
- const result = spawnSync("git", ["-C", this.host.cwd, "branch", "--show-current"], {
91
- encoding: "utf8",
92
- timeout: 150,
93
- });
94
- const branch = result.status === 0 ? result.stdout.trim() || undefined : undefined;
95
- this.gitBranchCache = { checkedAt: now, branch };
96
- return branch;
91
+ if (!this.gitBranchLookupInFlight) {
92
+ this.gitBranchLookupInFlight = true;
93
+ void this.refreshGitBranchName();
94
+ }
95
+ return this.gitBranchCache?.branch;
96
+ }
97
+ async refreshGitBranchName() {
98
+ const previous = this.gitBranchCache?.branch;
99
+ try {
100
+ const result = await runProcess("git", ["-C", this.host.cwd, "branch", "--show-current"], {
101
+ timeoutMs: 150,
102
+ maxBufferBytes: 1024,
103
+ });
104
+ const branch = result.status === 0 ? result.stdout.trim() || undefined : undefined;
105
+ this.gitBranchCache = { checkedAt: Date.now(), branch };
106
+ if (branch !== previous)
107
+ this.host.render();
108
+ }
109
+ finally {
110
+ this.gitBranchLookupInFlight = false;
111
+ }
97
112
  }
98
113
  startStatusBlink() {
99
114
  this.host.blinkController.setActive(STATUS_DOT_BLINK_KEY, true, {
@@ -1,6 +1,6 @@
1
1
  import type { AgentSession, AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
- import type { ImageContent } from "../input-editor.js";
3
- import type { Entry, SessionActivity, SubmittedUserMessage } from "./types.js";
2
+ import type { ImageContent } from "../../input-editor.js";
3
+ import type { Entry, SessionActivity, SubmittedUserMessage } from "../types.js";
4
4
  export type AppQueuedMessageControllerHost = {
5
5
  runtime(): AgentSessionRuntime | undefined;
6
6
  requireRuntime(): AgentSessionRuntime;
@@ -19,6 +19,7 @@ export type AppQueuedMessageControllerHost = {
19
19
  setInput(value: string): void;
20
20
  insertInput(value: string): void;
21
21
  attachImage(data: string, mimeType: string): void;
22
+ onDeferredUserMessagesChanged?(): void;
22
23
  };
23
24
  export declare class AppQueuedMessageController {
24
25
  private readonly host;
@@ -28,6 +29,8 @@ export declare class AppQueuedMessageController {
28
29
  private immediateSendInProgress;
29
30
  constructor(host: AppQueuedMessageControllerHost);
30
31
  reset(): void;
32
+ captureDeferredUserMessages(): SubmittedUserMessage[];
33
+ restoreDeferredUserMessages(messages: readonly SubmittedUserMessage[]): void;
31
34
  createSubmittedUserMessage(promptText: string, displayText: string, images: ImageContent[]): SubmittedUserMessage;
32
35
  submitUserMessage(message: SubmittedUserMessage): Promise<void>;
33
36
  sendUserMessageToSession(message: SubmittedUserMessage, options?: {
@@ -44,11 +47,12 @@ export declare class AppQueuedMessageController {
44
47
  cancelQueuedMessage(entryId: string): Promise<void>;
45
48
  editQueuedMessage(entryId: string): Promise<void>;
46
49
  sendQueuedMessageImmediately(entryId: string): Promise<void>;
50
+ private sendQueuedEntryImmediately;
47
51
  findQueuedEntry(entryId: string): Extract<Entry, {
48
52
  kind: "queued";
49
53
  }> | undefined;
50
54
  private shouldDeferUserMessage;
51
- private deferUserMessage;
55
+ deferUserMessage(message: SubmittedUserMessage): void;
52
56
  private rewriteSdkQueuedMessages;
53
57
  private takeQueuedEntryForInterruptedSend;
54
58
  private restoreSdkQueuedMessages;
@@ -59,4 +63,6 @@ export declare class AppQueuedMessageController {
59
63
  private requeueRemovedEntry;
60
64
  private restoreSubmittedMessageToEditor;
61
65
  private restorableSubmittedMessageText;
66
+ private cloneSubmittedUserMessage;
67
+ private notifyDeferredUserMessagesChanged;
62
68
  }
@@ -1,5 +1,5 @@
1
- import { createId } from "./id.js";
2
- import { stringifyUnknown, submittedUserDisplayText } from "./message-content.js";
1
+ import { createId } from "../id.js";
2
+ import { stringifyUnknown, submittedUserDisplayText } from "../rendering/message-content.js";
3
3
  export class AppQueuedMessageController {
4
4
  host;
5
5
  deferredUserMessages = [];
@@ -14,6 +14,14 @@ export class AppQueuedMessageController {
14
14
  this.promptSubmissionInFlight = false;
15
15
  this.flushingDeferredUserMessages = false;
16
16
  }
17
+ captureDeferredUserMessages() {
18
+ return this.deferredUserMessages.map((message) => this.cloneSubmittedUserMessage(message));
19
+ }
20
+ restoreDeferredUserMessages(messages) {
21
+ this.deferredUserMessages.length = 0;
22
+ this.deferredUserMessages.push(...messages.map((message) => this.cloneSubmittedUserMessage(message)));
23
+ this.updateQueuedMessageStatus();
24
+ }
17
25
  createSubmittedUserMessage(promptText, displayText, images) {
18
26
  return {
19
27
  id: createId("queued-user"),
@@ -55,8 +63,6 @@ export class AppQueuedMessageController {
55
63
  }
56
64
  if (this.totalQueuedMessageCount() > 0)
57
65
  this.updateQueuedMessageStatus();
58
- if (!this.flushingDeferredUserMessages)
59
- void this.flushDeferredUserMessages();
60
66
  }
61
67
  }
62
68
  async flushDeferredUserMessages() {
@@ -80,22 +86,14 @@ export class AppQueuedMessageController {
80
86
  const message = this.deferredUserMessages.shift();
81
87
  if (!message)
82
88
  break;
89
+ this.notifyDeferredUserMessagesChanged();
83
90
  this.updateQueuedMessageStatus();
84
- if (!activeSession.isStreaming && this.deferredUserMessages.length > 0) {
85
- void this.sendUserMessageToSession(message).catch((error) => {
86
- this.deferredUserMessages.unshift(message);
87
- this.updateQueuedMessageStatus();
88
- this.host.addEntry({ id: createId("error"), kind: "error", text: `Queued message failed: ${stringifyUnknown(error)}` });
89
- if (this.host.isRunning())
90
- this.host.render();
91
- });
92
- break;
93
- }
94
91
  try {
95
92
  await this.sendUserMessageToSession(message);
96
93
  }
97
94
  catch (error) {
98
95
  this.deferredUserMessages.unshift(message);
96
+ this.notifyDeferredUserMessagesChanged();
99
97
  this.updateQueuedMessageStatus();
100
98
  this.host.addEntry({ id: createId("error"), kind: "error", text: `Queued message failed: ${stringifyUnknown(error)}` });
101
99
  break;
@@ -103,17 +101,11 @@ export class AppQueuedMessageController {
103
101
  }
104
102
  }
105
103
  finally {
106
- const shouldRetryFlush = this.deferredUserMessages.length > 0 && Boolean(this.host.runtime()?.session) && !this.host.runtime()?.session.isCompacting;
107
104
  this.flushingDeferredUserMessages = false;
108
105
  if (this.totalQueuedMessageCount() > 0)
109
106
  this.updateQueuedMessageStatus();
110
107
  if (this.host.isRunning())
111
108
  this.host.render();
112
- if (shouldRetryFlush) {
113
- queueMicrotask(() => {
114
- void this.flushDeferredUserMessages();
115
- });
116
- }
117
109
  }
118
110
  }
119
111
  queuedMessageCounts() {
@@ -135,6 +127,8 @@ export class AppQueuedMessageController {
135
127
  const session = this.host.runtime()?.session;
136
128
  const sdkQueued = session?.clearQueue() ?? { steering: [], followUp: [] };
137
129
  const deferred = this.deferredUserMessages.splice(0);
130
+ if (deferred.length > 0)
131
+ this.notifyDeferredUserMessagesChanged();
138
132
  const restoredTexts = [
139
133
  ...sdkQueued.steering,
140
134
  ...deferred.map((message) => this.restorableSubmittedMessageText(message)),
@@ -186,6 +180,9 @@ export class AppQueuedMessageController {
186
180
  const entry = this.findQueuedEntry(entryId);
187
181
  if (!entry)
188
182
  throw new Error("Queued message is no longer available");
183
+ await this.sendQueuedEntryImmediately(entry);
184
+ }
185
+ async sendQueuedEntryImmediately(entry) {
189
186
  const session = this.host.requireRuntime().session;
190
187
  const shouldInterrupt = session.isStreaming || session.isCompacting;
191
188
  const taken = shouldInterrupt
@@ -224,8 +221,6 @@ export class AppQueuedMessageController {
224
221
  this.immediateSendInProgress = false;
225
222
  if (this.totalQueuedMessageCount() > 0)
226
223
  this.updateQueuedMessageStatus();
227
- if (!this.flushingDeferredUserMessages)
228
- void this.flushDeferredUserMessages();
229
224
  }
230
225
  }
231
226
  findQueuedEntry(entryId) {
@@ -233,11 +228,13 @@ export class AppQueuedMessageController {
233
228
  return entry?.kind === "queued" ? entry : undefined;
234
229
  }
235
230
  shouldDeferUserMessage(session) {
236
- return session.isCompacting || (!session.isStreaming && this.promptSubmissionInFlight);
231
+ return session.isStreaming || session.isCompacting || this.promptSubmissionInFlight;
237
232
  }
238
233
  deferUserMessage(message) {
239
234
  this.deferredUserMessages.push(message);
235
+ this.notifyDeferredUserMessagesChanged();
240
236
  this.updateQueuedMessageStatus();
237
+ this.host.showToast("Message queued; send it from the queue menu or status button", "info");
241
238
  this.host.render();
242
239
  }
243
240
  async rewriteSdkQueuedMessages(update) {
@@ -282,6 +279,7 @@ export class AppQueuedMessageController {
282
279
  if (!message)
283
280
  throw new Error("Queued message is no longer available");
284
281
  session.clearQueue();
282
+ this.notifyDeferredUserMessagesChanged();
285
283
  return { removed: message, sdkMessagesToRestore: sdkMessages };
286
284
  }
287
285
  const messages = entry.queueSource === "sdk-steering" ? sdkMessages.steering : sdkMessages.followUp;
@@ -334,6 +332,7 @@ export class AppQueuedMessageController {
334
332
  const [message] = this.deferredUserMessages.splice(entry.queueIndex, 1);
335
333
  if (!message)
336
334
  throw new Error("Queued message is no longer available");
335
+ this.notifyDeferredUserMessagesChanged();
337
336
  return message;
338
337
  }
339
338
  const removed = await this.rewriteSdkQueuedMessages((steering, followUp) => {
@@ -350,6 +349,7 @@ export class AppQueuedMessageController {
350
349
  if (typeof removed === "string")
351
350
  return;
352
351
  this.deferredUserMessages.splice(Math.min(entry.queueIndex, this.deferredUserMessages.length), 0, removed);
352
+ this.notifyDeferredUserMessagesChanged();
353
353
  return;
354
354
  }
355
355
  if (typeof removed !== "string")
@@ -374,4 +374,15 @@ export class AppQueuedMessageController {
374
374
  ? message.promptText.replace(/\[Image \d+(?:: [^\]]+)?\]\s*/g, "").trimEnd()
375
375
  : message.promptText.trimEnd();
376
376
  }
377
+ cloneSubmittedUserMessage(message) {
378
+ return {
379
+ id: message.id,
380
+ promptText: message.promptText,
381
+ displayText: message.displayText,
382
+ images: message.images.map((image) => ({ ...image })),
383
+ };
384
+ }
385
+ notifyDeferredUserMessagesChanged() {
386
+ this.host.onDeferredUserMessagesChanged?.();
387
+ }
377
388
  }
@@ -1,8 +1,8 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
- import { REQUEST_HISTORY_MAX_BYTES, REQUEST_HISTORY_MAX_ENTRIES, REQUEST_HISTORY_MAX_ENTRY_BYTES, REQUEST_HISTORY_VERSION, } from "./constants.js";
5
- import { isRecord } from "./guards.js";
4
+ import { REQUEST_HISTORY_MAX_BYTES, REQUEST_HISTORY_MAX_ENTRIES, REQUEST_HISTORY_MAX_ENTRY_BYTES, REQUEST_HISTORY_VERSION, } from "../constants.js";
5
+ import { isRecord } from "../guards.js";
6
6
  export class AppRequestHistory {
7
7
  host;
8
8
  entries = [];
@@ -0,0 +1,15 @@
1
+ import { type SessionInfo } from "@earendil-works/pi-coding-agent";
2
+ export type ResumeSessionLoadProgress = {
3
+ loaded: number;
4
+ total: number;
5
+ done: boolean;
6
+ };
7
+ export type ResumeSessionLoaderOptions = {
8
+ cwd: string;
9
+ sessionDir?: string;
10
+ initialChunkSize?: number;
11
+ chunkSize?: number;
12
+ signal?: AbortSignal;
13
+ onChunk(sessions: readonly SessionInfo[], progress: ResumeSessionLoadProgress): void;
14
+ };
15
+ export declare function loadResumeSessionsInChunks(options: ResumeSessionLoaderOptions): Promise<SessionInfo[]>;