poly-weaver 0.12.2 → 0.12.4

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 (89) hide show
  1. package/dist/ansi.d.ts +12 -0
  2. package/dist/ansi.d.ts.map +1 -1
  3. package/dist/ansi.js +334 -9
  4. package/dist/ansi.js.map +1 -1
  5. package/dist/offscreen/artifact-pane.d.ts +5 -0
  6. package/dist/offscreen/artifact-pane.d.ts.map +1 -1
  7. package/dist/offscreen/artifact-pane.js +20 -1
  8. package/dist/offscreen/artifact-pane.js.map +1 -1
  9. package/dist/offscreen/controller.d.ts +1 -0
  10. package/dist/offscreen/controller.d.ts.map +1 -1
  11. package/dist/offscreen/controller.js +105 -0
  12. package/dist/offscreen/controller.js.map +1 -1
  13. package/dist/offscreen/inquiry-pane.d.ts +2 -0
  14. package/dist/offscreen/inquiry-pane.d.ts.map +1 -1
  15. package/dist/offscreen/inquiry-pane.js +19 -0
  16. package/dist/offscreen/inquiry-pane.js.map +1 -1
  17. package/dist/offscreen/inquiry-sender.js +1 -1
  18. package/dist/offscreen/inquiry-sender.js.map +1 -1
  19. package/dist/offscreen/panes.d.ts +2 -0
  20. package/dist/offscreen/panes.d.ts.map +1 -1
  21. package/dist/offscreen/split-inquiry.d.ts +17 -0
  22. package/dist/offscreen/split-inquiry.d.ts.map +1 -1
  23. package/dist/offscreen/split-inquiry.js +107 -1
  24. package/dist/offscreen/split-inquiry.js.map +1 -1
  25. package/dist/providers/codex/plan-mode-activator.js +0 -3
  26. package/dist/providers/codex/plan-mode-activator.js.map +1 -1
  27. package/dist/providers/option-catalog.d.ts.map +1 -1
  28. package/dist/providers/option-catalog.js +23 -0
  29. package/dist/providers/option-catalog.js.map +1 -1
  30. package/dist/session/locator.d.ts +2 -2
  31. package/dist/session/locator.d.ts.map +1 -1
  32. package/dist/session/locator.js +2 -2
  33. package/dist/session/locator.js.map +1 -1
  34. package/dist/status-bar.d.ts +5 -2
  35. package/dist/status-bar.d.ts.map +1 -1
  36. package/dist/status-bar.js +18 -20
  37. package/dist/status-bar.js.map +1 -1
  38. package/dist/terminal/clipboard-write.d.ts +17 -0
  39. package/dist/terminal/clipboard-write.d.ts.map +1 -0
  40. package/dist/terminal/clipboard-write.js +58 -0
  41. package/dist/terminal/clipboard-write.js.map +1 -0
  42. package/dist/terminal/host.d.ts +90 -0
  43. package/dist/terminal/host.d.ts.map +1 -1
  44. package/dist/terminal/host.js +858 -17
  45. package/dist/terminal/host.js.map +1 -1
  46. package/dist/terminal/input-router.d.ts +21 -6
  47. package/dist/terminal/input-router.d.ts.map +1 -1
  48. package/dist/terminal/input-router.js +30 -8
  49. package/dist/terminal/input-router.js.map +1 -1
  50. package/dist/terminal/mouse-mode-tracker.d.ts +1 -0
  51. package/dist/terminal/mouse-mode-tracker.d.ts.map +1 -1
  52. package/dist/terminal/mouse-mode-tracker.js +3 -0
  53. package/dist/terminal/mouse-mode-tracker.js.map +1 -1
  54. package/dist/terminal/osc52.d.ts +1 -0
  55. package/dist/terminal/osc52.d.ts.map +1 -1
  56. package/dist/terminal/osc52.js +3 -0
  57. package/dist/terminal/osc52.js.map +1 -1
  58. package/dist/terminal/paste-submit.d.ts +1 -1
  59. package/dist/terminal/paste-submit.d.ts.map +1 -1
  60. package/dist/terminal/paste-submit.js +2 -2
  61. package/dist/terminal/paste-submit.js.map +1 -1
  62. package/dist/terminal/render.d.ts +5 -0
  63. package/dist/terminal/render.d.ts.map +1 -1
  64. package/dist/terminal/render.js +14 -4
  65. package/dist/terminal/render.js.map +1 -1
  66. package/dist/terminal/selection.d.ts +101 -0
  67. package/dist/terminal/selection.d.ts.map +1 -0
  68. package/dist/terminal/selection.js +517 -0
  69. package/dist/terminal/selection.js.map +1 -0
  70. package/dist/terminal/virtual-terminal.d.ts +5 -1
  71. package/dist/terminal/virtual-terminal.d.ts.map +1 -1
  72. package/dist/terminal/virtual-terminal.js +10 -0
  73. package/dist/terminal/virtual-terminal.js.map +1 -1
  74. package/dist/user/host-curate-prompt.d.ts +2 -0
  75. package/dist/user/host-curate-prompt.d.ts.map +1 -1
  76. package/dist/user/host-curate-prompt.js +239 -43
  77. package/dist/user/host-curate-prompt.js.map +1 -1
  78. package/dist/user/host-prompt.d.ts.map +1 -1
  79. package/dist/user/host-prompt.js +257 -14
  80. package/dist/user/host-prompt.js.map +1 -1
  81. package/dist/user/selectable-doc-pane.d.ts +13 -0
  82. package/dist/user/selectable-doc-pane.d.ts.map +1 -0
  83. package/dist/user/selectable-doc-pane.js +34 -0
  84. package/dist/user/selectable-doc-pane.js.map +1 -0
  85. package/dist/user/split-surface.d.ts +2 -0
  86. package/dist/user/split-surface.d.ts.map +1 -1
  87. package/dist/user/split-surface.js +17 -0
  88. package/dist/user/split-surface.js.map +1 -1
  89. package/package.json +1 -1
@@ -1,18 +1,24 @@
1
- import { ALT_SCREEN_OFF, ALT_SCREEN_ON, BEGIN_SYNC_UPDATE, CLEAR_SCREEN, CURSOR_HOME, END_SYNC_UPDATE, HIDE_CURSOR, MOUSE_OFF, QUERY_SYNC_UPDATE, RESET_SCROLL_REGION, SHOW_CURSOR, padOrClip, } from "../ansi.js";
1
+ import { ALT_SCREEN_OFF, ALT_SCREEN_ON, BEGIN_SYNC_UPDATE, CLEAR_SCREEN, CURSOR_HOME, END_SYNC_UPDATE, HIDE_CURSOR, MOUSE_OFF, QUERY_SYNC_UPDATE, RESET_SCROLL_REGION, SHOW_CURSOR, applySelectionBackgroundRange, padOrClip, } from "../ansi.js";
2
2
  import { computeTerminalLayout } from "./layout.js";
3
3
  import { VirtualTerminal } from "./virtual-terminal.js";
4
- import { renderChildViewportRows, renderViewportLines, renderStatusRowBody, shouldShowVtCursor, } from "./render.js";
4
+ import { composeAbsoluteRow, renderChildViewportRows, renderViewportLines, renderStatusRowBody, shouldShowVtCursor, } from "./render.js";
5
5
  import { BufferOrchestrationOutput, formatOrchestrationMessage, } from "./output.js";
6
- import { InputRouter, } from "./input-router.js";
6
+ import { InputRouter, parseSgrMouseFrame, } from "./input-router.js";
7
7
  import { createKeyEventSource } from "./key-event-source.js";
8
8
  import { ChildKittyNegotiator } from "./kitty-negotiator.js";
9
9
  import { MouseModeReconciler, MouseModeTracker, } from "./mouse-mode-tracker.js";
10
10
  import { buildOsc52Forward } from "./osc52.js";
11
+ import { copyToClipboard } from "./clipboard-write.js";
12
+ import { Selection, } from "./selection.js";
11
13
  import { buildSplitSurface, buildVerticalStackSurface, computeSplitMetrics, SPLIT_COLLAPSE_AT_COLS, } from "../user/split-surface.js";
12
14
  const MAIN_CHILD_MOUSE_SOURCE = "main-child";
13
15
  const HOST_MOUSE_SOURCE = "host-owned";
14
16
  const AGENT_SPLIT_MOUSE_SOURCE = "agent-split";
15
- const HOST_MOUSE_CAPTURE_MODES = [1000, 1006];
17
+ const HOST_MOUSE_CAPTURE_MODES = [1002, 1006];
18
+ const MULTI_CLICK_MS = 500;
19
+ const DRAG_SCROLL_INTERVAL_MS = 50;
20
+ const DRAG_SCROLL_LINES = 1;
21
+ const DRAG_EDGE_LOSS_MS = 80;
16
22
  /**
17
23
  * Parent-owned orchestration terminal host.
18
24
  *
@@ -38,6 +44,12 @@ export class TerminalHost {
38
44
  router;
39
45
  bufferOutput;
40
46
  onDump;
47
+ clipboardCopy;
48
+ now;
49
+ setIntervalFn;
50
+ clearIntervalFn;
51
+ setTimeoutFn;
52
+ clearTimeoutFn;
41
53
  interruptListeners = new Set();
42
54
  statusBar;
43
55
  childHandle;
@@ -80,6 +92,15 @@ export class TerminalHost {
80
92
  externalChildStatus;
81
93
  syncOutputSupported = false;
82
94
  lastFrameRows = null;
95
+ lastBaseRows = null;
96
+ selection = new Selection();
97
+ gestureOwner = "none";
98
+ lastClickTime = 0;
99
+ lastClickCell;
100
+ clickCount = 0;
101
+ dragScroll;
102
+ dragScrollTimer;
103
+ dragEdgeIntent;
83
104
  constructor(opts = {}) {
84
105
  this.stdin = opts.stdin ?? process.stdin;
85
106
  this.stdout = opts.stdout ?? process.stdout;
@@ -92,6 +113,16 @@ export class TerminalHost {
92
113
  this.bufferOutput.setOnChange(() => this.handleOutputChange());
93
114
  this.onDump = opts.onDump;
94
115
  this.injectedSource = opts.keyEventSource;
116
+ this.clipboardCopy = opts.copyToClipboard ?? copyToClipboard;
117
+ this.now = opts.now ?? Date.now;
118
+ this.setIntervalFn = opts.setIntervalFn ?? setInterval;
119
+ this.clearIntervalFn = opts.clearIntervalFn ?? clearInterval;
120
+ this.setTimeoutFn = opts.setTimeoutFn ?? setTimeout;
121
+ this.clearTimeoutFn = opts.clearTimeoutFn ?? clearTimeout;
122
+ const selectionCollaborator = {
123
+ hasSelection: () => this.hasSelection(),
124
+ handleEvent: (ev) => this.handleSelectionInput(ev),
125
+ };
95
126
  this.router = new InputRouter({
96
127
  onDump: opts.onDump,
97
128
  onInterrupt: () => this.fireInterrupt(),
@@ -102,8 +133,10 @@ export class TerminalHost {
102
133
  onScrollToBottom: () => this.handleScrollToBottom(),
103
134
  isChildAlternate: () => this.vt?.isAlternate === true,
104
135
  isChildMouseActive: () => this.childMouseTracker.hasActiveReportingMode(),
136
+ childWantsMotion: () => this.childMouseTracker.wantsMotion(),
105
137
  getChildKittyFlags: () => this.kittyNegotiator.activeFlags,
106
138
  getChildBracketedPaste: () => this.vt?.bracketedPaste === true,
139
+ selection: selectionCollaborator,
107
140
  onSyncOutputReport: (supported) => {
108
141
  this.syncOutputSupported = supported;
109
142
  },
@@ -172,6 +205,7 @@ export class TerminalHost {
172
205
  openAgentSplit(pane) {
173
206
  if (!this.hasAttachedChild())
174
207
  return;
208
+ this.clearSelection({ schedule: false });
175
209
  const state = this.agentSplit ?? { pane: undefined, open: false, collaborator: undefined };
176
210
  state.pane = pane;
177
211
  state.open = true;
@@ -185,6 +219,7 @@ export class TerminalHost {
185
219
  closeAgentSplit() {
186
220
  if (this.agentSplit?.open !== true)
187
221
  return;
222
+ this.clearSelection({ schedule: false });
188
223
  this.agentSplit.open = false;
189
224
  this.clearMouseModeSource(AGENT_SPLIT_MOUSE_SOURCE);
190
225
  this.applyChildViewportSize();
@@ -254,6 +289,7 @@ export class TerminalHost {
254
289
  if (!this.started || this.stopped)
255
290
  return;
256
291
  this.stopped = true;
292
+ this.resetDragScrollState();
257
293
  this.stopSource();
258
294
  if (this.resizeListener) {
259
295
  this.stdout.removeListener("resize", this.resizeListener);
@@ -367,6 +403,7 @@ export class TerminalHost {
367
403
  return;
368
404
  if (!this.childHandle)
369
405
  return;
406
+ this.clearSelection({ schedule: false });
370
407
  const collaborator = this.agentSplit?.collaborator;
371
408
  if (this.agentSplit?.open === true) {
372
409
  this.closeAgentSplit();
@@ -408,6 +445,7 @@ export class TerminalHost {
408
445
  if (!this.layout) {
409
446
  this.layout = this.computeLayout();
410
447
  }
448
+ this.clearSelection({ schedule: false });
411
449
  this.vt?.dispose();
412
450
  const rect = this.childViewportRect();
413
451
  this.vt = new VirtualTerminal(rect.cols, rect.rows, {
@@ -463,6 +501,7 @@ export class TerminalHost {
463
501
  detachParentUi(mode) {
464
502
  if (mode && this.parentUi !== mode)
465
503
  return;
504
+ this.resetDragScrollState();
466
505
  this.router.detachParentUi(this.parentUi);
467
506
  this.parentUi = undefined;
468
507
  this.invalidateFrameCache();
@@ -533,6 +572,7 @@ export class TerminalHost {
533
572
  }
534
573
  invalidateFrameCache() {
535
574
  this.lastFrameRows = null;
575
+ this.lastBaseRows = null;
536
576
  }
537
577
  /** Force an immediate render (synchronously composes one frame). */
538
578
  renderNow() {
@@ -556,24 +596,31 @@ export class TerminalHost {
556
596
  const parentOnly = !fullRender
557
597
  && surface !== undefined
558
598
  && surface.lines.length >= this.layout.childRows;
559
- const next = new Array(this.layout.rows).fill(null);
599
+ const baseRows = new Array(this.layout.rows).fill(null);
560
600
  if (!parentOnly) {
561
601
  const childRows = renderChildViewportRows(this.vt, this.layout);
562
602
  for (let i = 0; i < childRows.length; i++) {
563
- next[i] = childRows[i];
603
+ baseRows[i] = childRows[i];
564
604
  }
565
605
  }
566
606
  if (surface) {
567
- this.renderUiSurfaceRows(next, this.layout, surface);
607
+ this.renderUiSurfaceRows(baseRows, this.layout, surface);
568
608
  }
569
609
  // Warning strip is host-owned and lives in its own dedicated rows
570
610
  // (between the child viewport and the status bar), so it is rendered
571
611
  // independently of the parent UI surface and the child viewport.
572
- this.renderMessageRows(next, this.layout);
612
+ this.renderMessageRows(baseRows, this.layout);
573
613
  if (this.statusBar && this.layout.statusRow !== undefined) {
574
614
  const text = this.statusBar.render(this.layout.columns);
575
- next[this.layout.statusRow - 1] = renderStatusRowBody(this.layout, text);
615
+ baseRows[this.layout.statusRow - 1] = renderStatusRowBody(this.layout, text);
616
+ }
617
+ const prevBase = this.lastBaseRows;
618
+ for (let i = 0; i < baseRows.length; i++) {
619
+ if (baseRows[i] === null)
620
+ baseRows[i] = prevBase?.[i] ?? null;
576
621
  }
622
+ const displayRows = [...baseRows];
623
+ this.applySelectionHighlight(baseRows, displayRows);
577
624
  // Move the real cursor to the active owner of the frame. Host-owned
578
625
  // surfaces keep the historic hidden default unless they opt in; child-owned
579
626
  // frames mirror the child's own DECTCEM visibility.
@@ -604,25 +651,738 @@ export class TerminalHost {
604
651
  }
605
652
  const prev = this.lastFrameRows;
606
653
  let diffBody = HIDE_CURSOR;
607
- for (let i = 0; i < next.length; i++) {
608
- if (next[i] !== null) {
609
- if (prev === null || prev[i] !== next[i]) {
610
- diffBody += `\x1b[${i + 1};1H${next[i]}`;
654
+ for (let i = 0; i < displayRows.length; i++) {
655
+ if (displayRows[i] !== null) {
656
+ if (prev === null || prev[i] !== displayRows[i]) {
657
+ diffBody += `\x1b[${i + 1};1H${displayRows[i]}`;
611
658
  }
612
659
  }
613
660
  else {
614
- // Nothing painted this row this frame: carry the prior row forward
615
- // so `next` doubles as the cache for the following frame.
616
- next[i] = prev?.[i] ?? null;
661
+ displayRows[i] = prev?.[i] ?? null;
617
662
  }
618
663
  }
619
- this.lastFrameRows = next;
664
+ this.lastFrameRows = displayRows;
665
+ this.lastBaseRows = baseRows;
620
666
  diffBody += cursorPlacement;
621
667
  const frame = this.syncOutputSupported
622
668
  ? BEGIN_SYNC_UPDATE + diffBody + END_SYNC_UPDATE
623
669
  : diffBody;
624
670
  this.stdout.write(frame);
625
671
  }
672
+ applySelectionHighlight(baseRows, displayRows) {
673
+ if (!this.selection.hasSelection())
674
+ return;
675
+ const state = this.selection.getState();
676
+ if (!state)
677
+ return;
678
+ if (state.kind === "surface") {
679
+ const snapshot = this.snapshotRegionFromRows(state.region, baseRows);
680
+ this.selection.refreshSurfaceSnapshot(snapshot);
681
+ for (const span of this.selection.perRowSpans()) {
682
+ const idx = span.row - 1;
683
+ const base = baseRows[idx];
684
+ if (base === null || base === undefined)
685
+ continue;
686
+ displayRows[idx] = applySelectionBackgroundRange(base, span.startCol, span.endCol);
687
+ }
688
+ return;
689
+ }
690
+ if (state.kind === "agent-buffer") {
691
+ if (!this.vt || !this.selection.refreshAgentSnapshot((absRow) => composeAbsoluteRow(this.vt, absRow, state.composeCols))) {
692
+ this.selection.clear();
693
+ return;
694
+ }
695
+ const viewportY = this.vt.viewportY;
696
+ for (const span of this.selection.perRowSpans()) {
697
+ const screenRow = span.row - viewportY + 1;
698
+ if (screenRow < state.region.rowStart || screenRow > state.region.rowEnd)
699
+ continue;
700
+ const base = baseRows[screenRow - 1];
701
+ if (base === null || base === undefined)
702
+ continue;
703
+ displayRows[screenRow - 1] = applySelectionBackgroundRange(base, span.startCol, span.endCol);
704
+ }
705
+ return;
706
+ }
707
+ if (state.kind === "fork-buffer") {
708
+ const placement = this.parentUi && isHostParentUiMode(this.parentUi)
709
+ ? this.parentUi.forkSelectionPlacement?.()
710
+ : undefined;
711
+ if (!placement || !this.selection.refreshForkSnapshot((absRow) => composeAbsoluteRow(placement.vt, absRow, state.composeCols))) {
712
+ this.selection.clear();
713
+ return;
714
+ }
715
+ for (const span of this.selection.perRowSpans()) {
716
+ const screenRow = span.row - placement.vt.viewportY + placement.region.rowStart;
717
+ if (screenRow < placement.region.rowStart || screenRow > placement.region.rowEnd)
718
+ continue;
719
+ const base = baseRows[screenRow - 1];
720
+ if (base === null || base === undefined)
721
+ continue;
722
+ const startCol = Math.max(placement.region.colStart, span.startCol + placement.region.colStart - 1);
723
+ const endCol = Math.min(placement.region.colEnd, span.endCol + placement.region.colStart - 1);
724
+ if (startCol <= endCol) {
725
+ displayRows[screenRow - 1] = applySelectionBackgroundRange(base, startCol, endCol);
726
+ }
727
+ }
728
+ return;
729
+ }
730
+ const placement = this.docSurfacePlacement(state.pane);
731
+ if (!placement) {
732
+ this.selection.clear();
733
+ return;
734
+ }
735
+ for (const span of this.selection.perRowSpans()) {
736
+ const paneRow = span.row - placement.pane.getScrollTop() + 1;
737
+ const screenRow = placement.region.rowStart + paneRow - 1;
738
+ if (screenRow < placement.region.rowStart || screenRow > placement.region.rowEnd)
739
+ continue;
740
+ const base = baseRows[screenRow - 1];
741
+ if (base === null || base === undefined)
742
+ continue;
743
+ const startCol = Math.max(placement.region.colStart, span.startCol + placement.region.colStart - 1);
744
+ const endCol = Math.min(placement.region.colEnd, span.endCol + placement.region.colStart - 1);
745
+ if (startCol <= endCol) {
746
+ displayRows[screenRow - 1] = applySelectionBackgroundRange(base, startCol, endCol);
747
+ }
748
+ }
749
+ }
750
+ hasSelection() {
751
+ return this.selection.hasSelection();
752
+ }
753
+ handleSelectionInput(ev) {
754
+ if (ev.kind === "wheel") {
755
+ return "pass";
756
+ }
757
+ if (ev.kind === "control" && ev.byte === 0x03) {
758
+ if (!this.hasSelection())
759
+ return "pass";
760
+ void this.copySelection();
761
+ return "consume";
762
+ }
763
+ if (ev.kind === "escape") {
764
+ if (!this.hasSelection())
765
+ return "pass";
766
+ this.clearSelection();
767
+ return "consume";
768
+ }
769
+ const mouse = parseSgrMouseFrame(ev);
770
+ if (!mouse)
771
+ return "pass";
772
+ const point = { row: mouse.row, col: mouse.column };
773
+ const isMotion = mouse.final === "M" && (mouse.button & 32) !== 0;
774
+ const isLeftPress = mouse.final === "M" && mouse.button === 0;
775
+ const isLeftRelease = mouse.final === "m" && (mouse.button === 0 || mouse.button === 3);
776
+ const isRightPress = mouse.final === "M" && mouse.button === 2;
777
+ if (isLeftPress) {
778
+ this.clearSelection({ schedule: true });
779
+ const hit = this.hitTestSelectable(mouse.column, mouse.row);
780
+ if (!hit) {
781
+ this.gestureOwner = "child";
782
+ return "pass";
783
+ }
784
+ this.gestureOwner = "host";
785
+ const clickCount = this.nextClickCount(point);
786
+ if (hit.scrollTarget === "main-vt" || hit.scrollTarget === "agent-vt") {
787
+ if (!this.beginAgentBufferSelection(hit.region, point, clickCount)) {
788
+ this.gestureOwner = "child";
789
+ return "pass";
790
+ }
791
+ }
792
+ else if (hit.scrollTarget === "offscreen-inquiry") {
793
+ if (!this.beginForkBufferSelection(hit.region, point, clickCount)) {
794
+ this.gestureOwner = "child";
795
+ return "pass";
796
+ }
797
+ }
798
+ else if (hit.scrollTarget === "agent-artifact"
799
+ || hit.scrollTarget === "offscreen-artifact"
800
+ || hit.scrollTarget === "parent-doc-surface") {
801
+ if (!this.beginDocSurfaceSelection(hit.scrollTarget, hit.region, point, clickCount)) {
802
+ this.gestureOwner = "child";
803
+ return "pass";
804
+ }
805
+ }
806
+ else if (clickCount >= 3) {
807
+ const snapshot = this.snapshotRegion(hit.region);
808
+ this.selection.selectLine(hit.region, point, snapshot);
809
+ }
810
+ else if (clickCount === 2) {
811
+ const snapshot = this.snapshotRegion(hit.region);
812
+ this.selection.selectWord(hit.region, point, snapshot);
813
+ }
814
+ else {
815
+ const snapshot = this.snapshotRegion(hit.region);
816
+ this.selection.beginDrag(hit.region, point, snapshot);
817
+ }
818
+ this.dragScroll = {
819
+ region: hit.region,
820
+ target: hit.scrollTarget,
821
+ col: mouse.column,
822
+ row: mouse.row,
823
+ };
824
+ this.scheduleParentUiRender();
825
+ return "consume";
826
+ }
827
+ if (isMotion) {
828
+ if (this.gestureOwner === "host") {
829
+ const localPoint = this.localSelectionPoint(point);
830
+ this.selection.extendTo(localPoint, this.bufferSelectionComposeRow());
831
+ if (this.dragScroll) {
832
+ this.dragScroll.col = mouse.column;
833
+ this.dragScroll.row = mouse.row;
834
+ if (this.dragScroll.target !== "none"
835
+ && this.dragScrollPastEdge(this.dragScroll)) {
836
+ this.cancelDragEdgeIntent();
837
+ this.startDragScroll();
838
+ }
839
+ else if (this.dragScroll.target !== "none"
840
+ && this.updateDragEdgeIntent()) {
841
+ if (this.dragScroll.edgeIntent)
842
+ this.startDragScroll();
843
+ }
844
+ else {
845
+ this.cancelDragEdgeIntent();
846
+ this.stopDragScroll();
847
+ }
848
+ }
849
+ this.scheduleParentUiRender();
850
+ return "consume";
851
+ }
852
+ return "pass";
853
+ }
854
+ if (isLeftRelease) {
855
+ if (this.gestureOwner === "host") {
856
+ this.resetDragScrollState();
857
+ if (this.selection.mode === "char" && this.selection.isEmpty()) {
858
+ this.clearSelection({ schedule: true });
859
+ }
860
+ else {
861
+ this.scheduleParentUiRender();
862
+ }
863
+ this.gestureOwner = "none";
864
+ return "consume";
865
+ }
866
+ if (this.gestureOwner === "child")
867
+ this.gestureOwner = "none";
868
+ return "pass";
869
+ }
870
+ if (isRightPress) {
871
+ if (this.hasSelection()) {
872
+ void this.copySelection();
873
+ return "consume";
874
+ }
875
+ return this.hitTestSelectable(mouse.column, mouse.row) ? "consume" : "pass";
876
+ }
877
+ return "pass";
878
+ }
879
+ nextClickCount(point) {
880
+ const now = this.now();
881
+ const sameCell = this.lastClickCell?.row === point.row
882
+ && this.lastClickCell?.col === point.col;
883
+ this.clickCount = sameCell && now - this.lastClickTime <= MULTI_CLICK_MS
884
+ ? this.clickCount + 1
885
+ : 1;
886
+ this.lastClickTime = now;
887
+ this.lastClickCell = point;
888
+ return this.clickCount;
889
+ }
890
+ beginAgentBufferSelection(region, point, clickCount) {
891
+ if (!this.vt)
892
+ return false;
893
+ const normalized = normalizeSelectionRegion(region);
894
+ const allPaneMarkers = [];
895
+ for (let row = normalized.rowStart; row <= normalized.rowEnd; row++) {
896
+ const marker = this.vt.registerMarker(this.vt.viewportY + (row - 1));
897
+ if (marker)
898
+ allPaneMarkers.push(marker);
899
+ }
900
+ if (allPaneMarkers.length !== normalized.rowEnd - normalized.rowStart + 1) {
901
+ for (const marker of allPaneMarkers)
902
+ marker.dispose();
903
+ return false;
904
+ }
905
+ const composeCols = this.vt.cols;
906
+ const composeRow = (absRow) => composeAbsoluteRow(this.vt, absRow, composeCols);
907
+ const anchorPaneIdx = Math.min(Math.max(point.row - normalized.rowStart, 0), allPaneMarkers.length - 1);
908
+ if (clickCount === 2) {
909
+ this.selection.selectMarkerBufferWord("agent-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, point.col, composeRow);
910
+ }
911
+ else if (clickCount >= 3) {
912
+ this.selection.selectMarkerBufferLine("agent-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, composeRow);
913
+ }
914
+ else {
915
+ this.selection.beginMarkerBufferDrag("agent-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, point.col, composeRow);
916
+ }
917
+ return true;
918
+ }
919
+ beginForkBufferSelection(region, point, clickCount) {
920
+ const placement = this.parentUi && isHostParentUiMode(this.parentUi)
921
+ ? this.parentUi.forkSelectionPlacement?.()
922
+ : undefined;
923
+ if (!placement || placement.vt.isAlternate === true)
924
+ return false;
925
+ const normalized = normalizeSelectionRegion(placement.region);
926
+ const allPaneMarkers = [];
927
+ for (let row = normalized.rowStart; row <= normalized.rowEnd; row++) {
928
+ const marker = placement.vt.registerMarker(placement.vt.viewportY + (row - normalized.rowStart));
929
+ if (marker)
930
+ allPaneMarkers.push(marker);
931
+ }
932
+ if (allPaneMarkers.length !== normalized.rowEnd - normalized.rowStart + 1) {
933
+ for (const marker of allPaneMarkers)
934
+ marker.dispose();
935
+ return false;
936
+ }
937
+ const composeCols = placement.vt.cols;
938
+ const composeRow = (absRow) => composeAbsoluteRow(placement.vt, absRow, composeCols);
939
+ const anchorPaneIdx = Math.min(Math.max(point.row - normalized.rowStart, 0), allPaneMarkers.length - 1);
940
+ const localCol = this.localColumnForRegion(region, point.col);
941
+ if (clickCount === 2) {
942
+ this.selection.selectMarkerBufferWord("fork-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, localCol, composeRow);
943
+ }
944
+ else if (clickCount >= 3) {
945
+ this.selection.selectMarkerBufferLine("fork-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, composeRow);
946
+ }
947
+ else {
948
+ this.selection.beginMarkerBufferDrag("fork-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, localCol, composeRow);
949
+ }
950
+ return true;
951
+ }
952
+ beginDocSurfaceSelection(target, region, point, clickCount) {
953
+ const placement = this.docSurfacePlacementForTarget(target, point);
954
+ if (!placement)
955
+ return false;
956
+ const normalized = normalizeSelectionRegion(placement.region);
957
+ const localRow = Math.min(Math.max(point.row - normalized.rowStart + 1, 1), Math.max(1, normalized.rowEnd - normalized.rowStart + 1));
958
+ const docLine = placement.pane.getScrollTop() + localRow - 1;
959
+ const localCol = this.localColumnForRegion(region, point.col);
960
+ if (clickCount === 2) {
961
+ this.selection.selectDocWord(normalized, placement.pane, docLine, localCol);
962
+ }
963
+ else if (clickCount >= 3) {
964
+ this.selection.selectDocLine(normalized, placement.pane, docLine);
965
+ }
966
+ else {
967
+ this.selection.beginDocDrag(normalized, placement.pane, docLine, localCol);
968
+ }
969
+ return true;
970
+ }
971
+ bufferSelectionComposeRow() {
972
+ const state = this.selection.getState();
973
+ if (!state)
974
+ return undefined;
975
+ if (state.kind === "agent-buffer") {
976
+ if (!this.vt)
977
+ return undefined;
978
+ return (absRow) => composeAbsoluteRow(this.vt, absRow, state.composeCols);
979
+ }
980
+ if (state.kind === "fork-buffer") {
981
+ const placement = this.parentUi && isHostParentUiMode(this.parentUi)
982
+ ? this.parentUi.forkSelectionPlacement?.()
983
+ : undefined;
984
+ if (!placement)
985
+ return undefined;
986
+ return (absRow) => composeAbsoluteRow(placement.vt, absRow, state.composeCols);
987
+ }
988
+ return undefined;
989
+ }
990
+ clearSelection(opts = {}) {
991
+ this.resetDragScrollState();
992
+ if (!this.selection.clear())
993
+ return;
994
+ this.gestureOwner = "none";
995
+ if (opts.schedule !== false)
996
+ this.scheduleParentUiRender();
997
+ }
998
+ async copySelection() {
999
+ this.refreshSelectionBeforeCopy();
1000
+ const text = this.selection.extractText();
1001
+ if (text.length === 0)
1002
+ return;
1003
+ const result = await this.clipboardCopy(text, { stdout: this.stdout });
1004
+ if (result.osWritten) {
1005
+ this.statusBar?.flash(`Copied ${text.split("\n").length} line(s)`, 1500, "copy-success");
1006
+ this.clearSelection({ schedule: false });
1007
+ }
1008
+ else {
1009
+ this.statusBar?.flash("Copy failed (clipboard unavailable)", 1500, "copy-failure");
1010
+ }
1011
+ this.scheduleRender();
1012
+ }
1013
+ refreshSelectionBeforeCopy() {
1014
+ const state = this.selection.getState();
1015
+ if (!state)
1016
+ return;
1017
+ if (state.kind === "surface") {
1018
+ this.selection.refreshSurfaceSnapshot(this.snapshotRegion(state.region));
1019
+ return;
1020
+ }
1021
+ if (state.kind === "agent-buffer") {
1022
+ if (!this.vt || !this.selection.refreshAgentSnapshot((absRow) => composeAbsoluteRow(this.vt, absRow, state.composeCols))) {
1023
+ this.selection.clear();
1024
+ }
1025
+ return;
1026
+ }
1027
+ if (state.kind === "fork-buffer") {
1028
+ const placement = this.parentUi && isHostParentUiMode(this.parentUi)
1029
+ ? this.parentUi.forkSelectionPlacement?.()
1030
+ : undefined;
1031
+ if (!placement || !this.selection.refreshForkSnapshot((absRow) => composeAbsoluteRow(placement.vt, absRow, state.composeCols))) {
1032
+ this.selection.clear();
1033
+ }
1034
+ return;
1035
+ }
1036
+ if (state.kind === "doc-surface" && !this.docSurfacePlacement(state.pane)) {
1037
+ this.selection.clear();
1038
+ }
1039
+ }
1040
+ localSelectionPoint(point) {
1041
+ const state = this.selection.getState();
1042
+ if (state?.kind === "fork-buffer" || state?.kind === "doc-surface") {
1043
+ return { row: point.row, col: this.localColumnForRegion(state.region, point.col) };
1044
+ }
1045
+ return point;
1046
+ }
1047
+ localColumnForRegion(region, column) {
1048
+ const clamped = Math.min(Math.max(column, region.colStart), region.colEnd);
1049
+ return clamped - region.colStart + 1;
1050
+ }
1051
+ dragScrollPastEdge(drag) {
1052
+ return drag.row < drag.region.rowStart || drag.row > drag.region.rowEnd;
1053
+ }
1054
+ dragScrollDirection(drag) {
1055
+ if (drag.row < drag.region.rowStart)
1056
+ return "back";
1057
+ if (drag.row > drag.region.rowEnd)
1058
+ return "forward";
1059
+ return drag.edgeIntent;
1060
+ }
1061
+ startDragScroll() {
1062
+ if (this.dragScrollTimer)
1063
+ return;
1064
+ if (!this.dragScrollTick())
1065
+ return;
1066
+ if (this.dragScrollTimer)
1067
+ return;
1068
+ const timer = this.setIntervalFn(() => {
1069
+ this.dragScrollTick();
1070
+ }, DRAG_SCROLL_INTERVAL_MS);
1071
+ this.dragScrollTimer = timer;
1072
+ const maybeTimer = timer;
1073
+ maybeTimer.unref?.();
1074
+ }
1075
+ stopDragScroll() {
1076
+ const timer = this.dragScrollTimer;
1077
+ if (!timer)
1078
+ return;
1079
+ this.clearIntervalFn(timer);
1080
+ this.dragScrollTimer = undefined;
1081
+ }
1082
+ resetDragScrollState() {
1083
+ this.stopDragScroll();
1084
+ this.cancelDragEdgeIntent();
1085
+ this.dragScroll = undefined;
1086
+ }
1087
+ cancelDragEdgeIntent() {
1088
+ if (this.dragEdgeIntent) {
1089
+ this.clearTimeoutFn(this.dragEdgeIntent.timer);
1090
+ this.dragEdgeIntent = undefined;
1091
+ }
1092
+ if (this.dragScroll)
1093
+ this.dragScroll.edgeIntent = undefined;
1094
+ }
1095
+ updateDragEdgeIntent() {
1096
+ const drag = this.dragScroll;
1097
+ const direction = drag ? this.terminalEdgeDragDirection(drag) : undefined;
1098
+ if (!drag || !direction) {
1099
+ this.cancelDragEdgeIntent();
1100
+ return false;
1101
+ }
1102
+ if (drag.edgeIntent === direction)
1103
+ return true;
1104
+ if (this.dragEdgeIntent?.direction === direction)
1105
+ return true;
1106
+ this.cancelDragEdgeIntent();
1107
+ const timer = this.setTimeoutFn(() => {
1108
+ if (!this.dragScroll || this.gestureOwner !== "host")
1109
+ return;
1110
+ if (this.terminalEdgeDragDirection(this.dragScroll) !== direction)
1111
+ return;
1112
+ this.dragEdgeIntent = undefined;
1113
+ this.dragScroll.edgeIntent = direction;
1114
+ this.startDragScroll();
1115
+ }, DRAG_EDGE_LOSS_MS);
1116
+ const maybeTimer = timer;
1117
+ maybeTimer.unref?.();
1118
+ this.dragEdgeIntent = { direction, timer };
1119
+ return true;
1120
+ }
1121
+ terminalEdgeDragDirection(drag) {
1122
+ if (drag.target === "none")
1123
+ return undefined;
1124
+ const layout = this.layout;
1125
+ if (!layout)
1126
+ return undefined;
1127
+ if (drag.row === drag.region.rowStart && drag.region.rowStart === 1)
1128
+ return "back";
1129
+ if (drag.row === drag.region.rowEnd && drag.region.rowEnd === layout.rows)
1130
+ return "forward";
1131
+ return undefined;
1132
+ }
1133
+ dragScrollTick() {
1134
+ const drag = this.dragScroll;
1135
+ if (!drag || drag.target === "none") {
1136
+ this.stopDragScroll();
1137
+ return false;
1138
+ }
1139
+ const direction = this.dragScrollDirection(drag);
1140
+ if (!direction) {
1141
+ this.stopDragScroll();
1142
+ return false;
1143
+ }
1144
+ const moved = this.scrollSurface(direction);
1145
+ if (moved === 0) {
1146
+ this.stopDragScroll();
1147
+ return false;
1148
+ }
1149
+ if (!this.growSelectionAfterDragScroll(direction)) {
1150
+ this.stopDragScroll();
1151
+ return false;
1152
+ }
1153
+ this.scheduleRender();
1154
+ return true;
1155
+ }
1156
+ scrollSurface(direction) {
1157
+ const drag = this.dragScroll;
1158
+ if (!drag)
1159
+ return 0;
1160
+ switch (drag.target) {
1161
+ case "main-vt":
1162
+ case "agent-vt": {
1163
+ if (!this.vt)
1164
+ return 0;
1165
+ const before = this.vt.viewportY;
1166
+ if (direction === "back")
1167
+ this.vt.scrollBack(DRAG_SCROLL_LINES);
1168
+ else
1169
+ this.vt.scrollForward(DRAG_SCROLL_LINES);
1170
+ return Math.abs(this.vt.viewportY - before);
1171
+ }
1172
+ case "agent-artifact": {
1173
+ const placement = this.agentSplitArtifactPlacement();
1174
+ return placement?.pane.scrollLines(direction, DRAG_SCROLL_LINES) ?? 0;
1175
+ }
1176
+ case "offscreen-inquiry":
1177
+ case "offscreen-artifact": {
1178
+ if (!this.parentUi || !isHostParentUiMode(this.parentUi))
1179
+ return 0;
1180
+ const column = Math.min(Math.max(drag.col, drag.region.colStart), drag.region.colEnd);
1181
+ return this.parentUi.scrollSelectableSurfaceAt?.(column, drag.row, direction) ?? 0;
1182
+ }
1183
+ case "parent-doc-surface": {
1184
+ const state = this.selection.getState();
1185
+ if (state?.kind !== "doc-surface")
1186
+ return 0;
1187
+ return scrollDocSurfacePane(state.pane, direction, DRAG_SCROLL_LINES);
1188
+ }
1189
+ case "none":
1190
+ return 0;
1191
+ }
1192
+ }
1193
+ growSelectionAfterDragScroll(direction) {
1194
+ const drag = this.dragScroll;
1195
+ if (!drag)
1196
+ return false;
1197
+ const edgeScreenRow = direction === "back" ? drag.region.rowStart : drag.region.rowEnd;
1198
+ const focusColLocal = this.localColumnForRegion(drag.region, drag.col);
1199
+ if (drag.target === "main-vt" || drag.target === "agent-vt") {
1200
+ if (!this.vt)
1201
+ return false;
1202
+ const state = this.selection.getState();
1203
+ if (state?.kind !== "agent-buffer")
1204
+ return false;
1205
+ const absRow = this.vt.viewportY + (edgeScreenRow - drag.region.rowStart);
1206
+ const marker = this.vt.registerMarker(absRow);
1207
+ if (!marker)
1208
+ return false;
1209
+ this.selection.growAgentBufferByMarker(marker, direction, focusColLocal, (row) => composeAbsoluteRow(this.vt, row, state.composeCols));
1210
+ return true;
1211
+ }
1212
+ if (drag.target === "offscreen-inquiry") {
1213
+ const placement = this.parentUi && isHostParentUiMode(this.parentUi)
1214
+ ? this.parentUi.forkSelectionPlacement?.()
1215
+ : undefined;
1216
+ const state = this.selection.getState();
1217
+ if (!placement || state?.kind !== "fork-buffer")
1218
+ return false;
1219
+ const absRow = placement.vt.viewportY + (edgeScreenRow - placement.region.rowStart);
1220
+ const marker = placement.vt.registerMarker(absRow);
1221
+ if (!marker)
1222
+ return false;
1223
+ this.selection.growForkByMarker(marker, direction, focusColLocal, (row) => composeAbsoluteRow(placement.vt, row, state.composeCols));
1224
+ return true;
1225
+ }
1226
+ if (drag.target === "agent-artifact"
1227
+ || drag.target === "offscreen-artifact"
1228
+ || drag.target === "parent-doc-surface") {
1229
+ const state = this.selection.getState();
1230
+ if (state?.kind !== "doc-surface")
1231
+ return false;
1232
+ const placement = this.docSurfacePlacement(state.pane);
1233
+ if (!placement)
1234
+ return false;
1235
+ const edgeDocLine = placement.pane.getScrollTop()
1236
+ + (direction === "back" ? 0 : placement.pane.getViewportRows() - 1);
1237
+ this.selection.growDocSurface(direction, focusColLocal, edgeDocLine);
1238
+ return true;
1239
+ }
1240
+ return false;
1241
+ }
1242
+ hitTestSelectable(column, row) {
1243
+ const layout = this.layout;
1244
+ if (!layout)
1245
+ return null;
1246
+ if (column < 1 || column > layout.columns || row < 1 || row > layout.rows)
1247
+ return null;
1248
+ if (layout.statusRow !== undefined && row === layout.statusRow)
1249
+ return null;
1250
+ if (this.vt?.isAlternate !== true
1251
+ &&
1252
+ layout.warningRows > 0
1253
+ && layout.warningStartRow !== undefined
1254
+ && this.bufferOutput.snapshot().length > 0) {
1255
+ const rowStart = layout.warningStartRow;
1256
+ const rowEnd = layout.warningStartRow + layout.warningRows - 1;
1257
+ if (row >= rowStart && row <= rowEnd) {
1258
+ return {
1259
+ source: "surface",
1260
+ scrollTarget: "none",
1261
+ region: { rowStart, rowEnd, colStart: 1, colEnd: layout.columns },
1262
+ };
1263
+ }
1264
+ }
1265
+ if (this.isAgentSplitOpen()) {
1266
+ return this.hitTestAgentSplitPane(column, row);
1267
+ }
1268
+ if (this.parentUi && isHostParentUiMode(this.parentUi)) {
1269
+ const perPane = this.parentUi.selectionRegionAt?.(column, row);
1270
+ if (perPane !== undefined) {
1271
+ if (!perPane)
1272
+ return null;
1273
+ return {
1274
+ source: "surface",
1275
+ scrollTarget: this.parentUi.selectionTargetAt?.(column, row) ?? "none",
1276
+ region: perPane,
1277
+ };
1278
+ }
1279
+ }
1280
+ if (this.vt?.isAlternate === true)
1281
+ return null;
1282
+ if (this.parentUi && isHostParentUiMode(this.parentUi)) {
1283
+ const surface = this.parentUi.getSurface();
1284
+ const maxLines = Math.min(surface.lines.length, layout.childRows);
1285
+ if (maxLines > 0) {
1286
+ const rowStart = layout.childRows - maxLines + 1;
1287
+ const rowEnd = layout.childRows;
1288
+ if (row >= rowStart && row <= rowEnd) {
1289
+ return {
1290
+ source: "surface",
1291
+ scrollTarget: "none",
1292
+ region: { rowStart, rowEnd, colStart: 1, colEnd: layout.columns },
1293
+ };
1294
+ }
1295
+ }
1296
+ return null;
1297
+ }
1298
+ if (this.hasAttachedChild() && layout.childRows > 0) {
1299
+ return {
1300
+ source: "agent-buffer",
1301
+ scrollTarget: "main-vt",
1302
+ region: {
1303
+ rowStart: 1,
1304
+ rowEnd: layout.childRows,
1305
+ colStart: 1,
1306
+ colEnd: layout.columns,
1307
+ },
1308
+ };
1309
+ }
1310
+ return null;
1311
+ }
1312
+ hitTestAgentSplitPane(column, row) {
1313
+ const layout = this.layout;
1314
+ if (!layout)
1315
+ return null;
1316
+ const pane = this.routeAgentSplitMouse(column, row);
1317
+ if (!pane)
1318
+ return null;
1319
+ const metrics = computeSplitMetrics({
1320
+ cols: layout.columns,
1321
+ rows: layout.childRows,
1322
+ collapseAt: SPLIT_COLLAPSE_AT_COLS,
1323
+ });
1324
+ if (!metrics.collapsed) {
1325
+ if (pane === "agent") {
1326
+ if (this.vt?.isAlternate === true)
1327
+ return null;
1328
+ return {
1329
+ source: "agent-buffer",
1330
+ scrollTarget: "agent-vt",
1331
+ region: {
1332
+ rowStart: 1,
1333
+ rowEnd: layout.childRows,
1334
+ colStart: 1,
1335
+ colEnd: metrics.leftWidth,
1336
+ },
1337
+ };
1338
+ }
1339
+ return {
1340
+ source: "surface",
1341
+ scrollTarget: "agent-artifact",
1342
+ region: {
1343
+ rowStart: 1,
1344
+ rowEnd: layout.childRows,
1345
+ colStart: metrics.leftWidth + 2,
1346
+ colEnd: layout.columns,
1347
+ },
1348
+ };
1349
+ }
1350
+ const topRows = this.collapsedTopRows(layout.childRows);
1351
+ if (pane === "agent") {
1352
+ if (this.vt?.isAlternate === true)
1353
+ return null;
1354
+ return {
1355
+ source: "agent-buffer",
1356
+ scrollTarget: "agent-vt",
1357
+ region: {
1358
+ rowStart: 1,
1359
+ rowEnd: topRows,
1360
+ colStart: 1,
1361
+ colEnd: layout.columns,
1362
+ },
1363
+ };
1364
+ }
1365
+ return {
1366
+ source: "surface",
1367
+ scrollTarget: "agent-artifact",
1368
+ region: {
1369
+ rowStart: topRows + 1,
1370
+ rowEnd: layout.childRows,
1371
+ colStart: 1,
1372
+ colEnd: layout.columns,
1373
+ },
1374
+ };
1375
+ }
1376
+ snapshotRegion(region) {
1377
+ return this.snapshotRegionFromRows(region, this.lastBaseRows);
1378
+ }
1379
+ snapshotRegionFromRows(region, rows) {
1380
+ const snapshot = [];
1381
+ for (let row = region.rowStart; row <= region.rowEnd; row++) {
1382
+ snapshot.push(rows?.[row - 1] ?? "");
1383
+ }
1384
+ return snapshot;
1385
+ }
626
1386
  /**
627
1387
  * Schedule a coalesced full render; multiple events in the same tick
628
1388
  * produce one frame. Use this for any change that can affect the child
@@ -777,6 +1537,71 @@ export class TerminalHost {
777
1537
  : null;
778
1538
  return buildVerticalStackSurface(agent, artifact, { cols: layout.childCols, rows: layout.childRows, topRows });
779
1539
  }
1540
+ agentSplitArtifactPlacement() {
1541
+ if (!this.isAgentSplitOpen() || !this.layout || this.agentSplit?.pane === undefined) {
1542
+ return undefined;
1543
+ }
1544
+ const layout = this.layout;
1545
+ const metrics = computeSplitMetrics({
1546
+ cols: layout.columns,
1547
+ rows: layout.childRows,
1548
+ collapseAt: SPLIT_COLLAPSE_AT_COLS,
1549
+ });
1550
+ if (!metrics.collapsed) {
1551
+ return {
1552
+ region: {
1553
+ rowStart: 1,
1554
+ rowEnd: layout.childRows,
1555
+ colStart: metrics.leftWidth + 2,
1556
+ colEnd: layout.columns,
1557
+ },
1558
+ pane: this.agentSplit.pane,
1559
+ };
1560
+ }
1561
+ const topRows = this.collapsedTopRows(layout.childRows);
1562
+ if (topRows >= layout.childRows)
1563
+ return undefined;
1564
+ return {
1565
+ region: {
1566
+ rowStart: topRows + 1,
1567
+ rowEnd: layout.childRows,
1568
+ colStart: 1,
1569
+ colEnd: layout.columns,
1570
+ },
1571
+ pane: this.agentSplit.pane,
1572
+ };
1573
+ }
1574
+ docSurfacePlacementForTarget(target, point) {
1575
+ if (target === "agent-artifact")
1576
+ return this.agentSplitArtifactPlacement();
1577
+ if (!this.parentUi || !isHostParentUiMode(this.parentUi))
1578
+ return undefined;
1579
+ if (point) {
1580
+ const placement = this.parentUi.docSurfaceSelectionPlacementAt?.(point.col, point.row);
1581
+ if (placement)
1582
+ return placement;
1583
+ }
1584
+ if (target === "parent-doc-surface")
1585
+ return undefined;
1586
+ return this.parentUi.artifactSelectionPlacement?.();
1587
+ }
1588
+ docSurfacePlacement(pane) {
1589
+ const agentPlacement = this.agentSplitArtifactPlacement();
1590
+ if (agentPlacement?.pane === pane)
1591
+ return agentPlacement;
1592
+ const parentPlacements = this.parentUi && isHostParentUiMode(this.parentUi)
1593
+ ? this.parentUi.docSurfaceSelectionPlacements?.()
1594
+ : undefined;
1595
+ const parentPlacement = parentPlacements?.find((placement) => placement.pane === pane);
1596
+ if (parentPlacement)
1597
+ return parentPlacement;
1598
+ const offscreenPlacement = this.parentUi && isHostParentUiMode(this.parentUi)
1599
+ ? this.parentUi.artifactSelectionPlacement?.()
1600
+ : undefined;
1601
+ if (offscreenPlacement?.pane === pane)
1602
+ return offscreenPlacement;
1603
+ return undefined;
1604
+ }
780
1605
  routeAgentSplitMouse(column, row) {
781
1606
  if (!this.isAgentSplitOpen() || !this.layout)
782
1607
  return null;
@@ -809,6 +1634,8 @@ export class TerminalHost {
809
1634
  }
810
1635
  }
811
1636
  handleChildAlternateChange(isAlternate) {
1637
+ if (isAlternate)
1638
+ this.clearSelection({ schedule: false });
812
1639
  this.agentSplit?.collaborator?.onChildAlternateChange?.(isAlternate);
813
1640
  }
814
1641
  renderUiSurfaceRows(rows, layout, surface) {
@@ -841,6 +1668,7 @@ export class TerminalHost {
841
1668
  handleResize(size) {
842
1669
  if (!this.isActive)
843
1670
  return;
1671
+ this.clearSelection({ schedule: false });
844
1672
  let next;
845
1673
  if (size) {
846
1674
  // Source-provided dimensions are already post-resize and may be
@@ -900,6 +1728,7 @@ export class TerminalHost {
900
1728
  this.layout = next;
901
1729
  if (prev !== undefined
902
1730
  && (prev.childCols !== next.childCols || prev.childRows !== next.childRows)) {
1731
+ this.clearSelection({ schedule: false });
903
1732
  this.invalidateFrameCache();
904
1733
  this.applyChildViewportSize();
905
1734
  this.notifyParentUiResize();
@@ -923,6 +1752,18 @@ export class TerminalHost {
923
1752
  function isHostParentUiMode(mode) {
924
1753
  return typeof mode.getSurface === "function";
925
1754
  }
1755
+ function scrollDocSurfacePane(pane, direction, lines) {
1756
+ const scrollable = pane;
1757
+ return scrollable.scrollLines?.(direction, lines) ?? 0;
1758
+ }
1759
+ function normalizeSelectionRegion(region) {
1760
+ return {
1761
+ rowStart: Math.min(region.rowStart, region.rowEnd),
1762
+ rowEnd: Math.max(region.rowStart, region.rowEnd),
1763
+ colStart: Math.min(region.colStart, region.colEnd),
1764
+ colEnd: Math.max(region.colStart, region.colEnd),
1765
+ };
1766
+ }
926
1767
  /**
927
1768
  * Map a surface-relative cursor position to a 1-based screen position.
928
1769
  *